Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Price slider implementation #4376

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/peregrine/lib/talons/FilterModal/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
);
Expand Down
11 changes: 9 additions & 2 deletions packages/peregrine/lib/talons/FilterModal/useFilterBlock.js
Original file line number Diff line number Diff line change
@@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(
{
Expand Down
131 changes: 100 additions & 31 deletions packages/venia-ui/lib/components/FilterModal/FilterList/filterList.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Fragment, useMemo } from 'react';
import React, { Fragment, useMemo, useState, useRef } from 'react';
import { array, func, number, shape, string } from 'prop-types';
import { useIntl } from 'react-intl';
import setValidator from '@magento/peregrine/lib/validators/set';
Expand All @@ -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();

Expand All @@ -22,13 +24,65 @@ 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') {
debugger;
var minRange = Number(items[0].value.split('_')[0]);
var maxRange = Number(items[items.length - 1].value.split('_')[1]);
}

const [value, setValue] = useState([
minRange ? minRange : null,
maxRange ? maxRange : null
]);

const [isSliding, setIsSliding] = useState(false); // Track whether the user is sliding
const sliderTimeoutRef = useRef(null);

const handleSliderStart = () => {
setIsSliding(true); // User started sliding
if (sliderTimeoutRef.current) {
clearTimeout(sliderTimeoutRef.current);
}
};

const handleSliderEnd = newValue => {
setIsSliding(false); // User stopped sliding
// Call the actual onChange only after a brief delay (debounce)
sliderTimeoutRef.current = setTimeout(() => {
handleChange(newValue);
}, 300); // Delay of 300ms after the user stops interacting with the slider
};

const handleChange = newValue => {
// Remove the previous price filter from the URL
const test = String(search).split('&');
const filters = test.filter(element => {
return !element.includes('price');
});
const newSearch = filters.join('&');
const nextParams = new URLSearchParams(newSearch);

// Append the new price filter range in the URL
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}`);

// Write price filter state to history
history.push({ pathname, search: String(nextParams) });

// Set new value to the slider when the slider stops
setValue(newValue);
};

// Memoize item creation
const itemElements = useMemo(() => {
if (filterFrontendInput === 'boolean') {
const key = `item-${group}`;
Expand All @@ -51,36 +105,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 = (
<li
key={key}
className={classes.item}
data-cy="FilterList-item"
>
<FilterItem
filterApi={filterApi}
filterState={filterState}
group={group}
item={item}
onApply={onApply}
if (name === 'Price') {
return (
<div className={classes.root}>
<RangeSlider
min={minRange}
max={maxRange}
onChange={handleChange}
/>
</li>
</div>
);
} else {
return items.map((item, index) => {
const { title, value } = item;
const key = `item-${group}-${value}`;

// 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;
});
if (!isListExpanded && index >= itemCountToShow) {
return null;
}

const element = (
<li
key={key}
className={classes.item}
data-cy="FilterList-item"
>
<FilterItem
filterApi={filterApi}
filterState={filterState}
group={group}
item={item}
onApply={onApply}
/>
</li>
);
labels.set(element, title.toUpperCase());
return element;
});
}
}, [
classes,
filterApi,
Expand All @@ -91,7 +153,14 @@ const FilterList = props => {
items,
isListExpanded,
itemCountToShow,
onApply
onApply,
history,
minRange,
maxRange,
pathname,
search,
value,
isSliding
]);

const showMoreLessItem = useMemo(() => {
Expand Down
3 changes: 2 additions & 1 deletion packages/venia-ui/lib/components/FilterModal/filterBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const FilterBlock = props => {
const talonProps = useFilterBlock({
filterState,
items,
initialOpen
initialOpen,
group
});
const { handleClick, isExpanded } = talonProps;
const iconSrc = isExpanded ? ArrowUp : ArrowDown;
Expand Down
28 changes: 28 additions & 0 deletions packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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) => {
Expand Down Expand Up @@ -115,6 +142,7 @@ const FilterSidebar = props => {
/>
</h2>
</div>
{priceFilters}
<CurrentFilters
filterApi={filterApi}
filterNames={filterNames}
Expand Down
Loading
Loading