diff --git a/packages/react-instantsearch/src/components/CurrentRefinements.css b/packages/react-instantsearch/src/components/CurrentRefinements.css
index 2966f2595c..77ceb98585 100644
--- a/packages/react-instantsearch/src/components/CurrentRefinements.css
+++ b/packages/react-instantsearch/src/components/CurrentRefinements.css
@@ -10,5 +10,8 @@
.itemLabel {
}
+.itemParent {
+}
+
.itemClear {
}
\ No newline at end of file
diff --git a/packages/react-instantsearch/src/components/CurrentRefinements.js b/packages/react-instantsearch/src/components/CurrentRefinements.js
index 1a5f548e67..13d5bb9162 100644
--- a/packages/react-instantsearch/src/components/CurrentRefinements.js
+++ b/packages/react-instantsearch/src/components/CurrentRefinements.js
@@ -27,17 +27,32 @@ class CurrentRefinements extends Component {
{items.map(item =>
{item.label}
-
+ {item.items ?
+ item.items.map(nestedItem =>
+
+
+ {nestedItem.label}
+
+
+
) :
+
}
)}
diff --git a/packages/react-instantsearch/src/connectors/connectCurrentRefinements.js b/packages/react-instantsearch/src/connectors/connectCurrentRefinements.js
index 52b042673e..5a84840ace 100644
--- a/packages/react-instantsearch/src/connectors/connectCurrentRefinements.js
+++ b/packages/react-instantsearch/src/connectors/connectCurrentRefinements.js
@@ -8,7 +8,7 @@ import createConnector from '../core/createConnector';
* @kind connector
* @category connector
* @providedPropType {function} refine - a function to remove a single filter
- * @providedPropType {array.<{key: string, label: string}>} items - all the filters, the key for calling the refine prop function, label is for the display.
+ * @providedPropType {array.<{label: string, attributeName: string, currentRefinement: string || object, items: array, value: function}>} items - all the filters, the `value` is to pass to the `refine` function for removing all currentrefinements, `label` is for the display. When existing several refinements for the same atribute name, then you get a nested `items` object that contains a `label` and a `value` function to use to remove a single filter. `attributeName` and `currentRefinement` are metadata containing row values.
*/
export default createConnector({
displayName: 'AlgoliaCurrentRefinements',
@@ -16,12 +16,14 @@ export default createConnector({
getProps(props, state, search, metadata) {
return {
items: metadata.reduce((res, meta) =>
- typeof meta.filters !== 'undefined' ? res.concat(meta.filters) : res
- , []),
+ typeof meta.items !== 'undefined' ? res.concat(meta.items) : res
+ , []),
};
},
- refine(props, state, filters) {
- return filters.reduce((res, filter) => filter.clear(res), state);
+ refine(props, state, items) {
+ // `value` corresponds to our internal clear function computed in each connector metadata.
+ const refinementsToClear = items instanceof Array ? items.map(item => item.value) : [items];
+ return refinementsToClear.reduce((res, clear) => clear(res), state);
},
});
diff --git a/packages/react-instantsearch/src/connectors/connectCurrentRefinements.test.js b/packages/react-instantsearch/src/connectors/connectCurrentRefinements.test.js
index 00ac2bfb63..f80c97ca59 100644
--- a/packages/react-instantsearch/src/connectors/connectCurrentRefinements.test.js
+++ b/packages/react-instantsearch/src/connectors/connectCurrentRefinements.test.js
@@ -8,16 +8,16 @@ const {refine, getProps} = connect;
describe('connectCurrentRefinements', () => {
it('provides the correct props to the component', () => {
const props = getProps(null, null, null, [
- {filters: ['one']},
- {filters: ['two']},
- {filters: ['three']},
+ {items: ['one']},
+ {items: ['two']},
+ {items: ['three']},
]);
expect(props.items).toEqual(['one', 'two', 'three']);
});
it('refine applies the selected filters clear method on state', () => {
const state = refine(null, {wow: 'sweet'}, [{
- clear: nextState => ({...nextState, cool: 'neat'}),
+ value: nextState => ({...nextState, cool: 'neat'}),
}]);
expect(state).toEqual({wow: 'sweet', cool: 'neat'});
});
diff --git a/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.js b/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.js
index 2949714042..c310f4b9f1 100644
--- a/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.js
+++ b/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.js
@@ -80,7 +80,7 @@ function transformValue(value, limit, props, state) {
* @propType {string} [separator='>'] - Specifies the level separator used in the data.
* @propType {string[]} [rootPath=null] - The already selected and hidden path.
* @propType {boolean} [showParentLevel=true] - Flag to set if the parent level should be displayed.
- * @providedPropType {function} refine - a function to remove a single filter
+ * @providedPropType {function} refine - a function to toggle a refinement
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied
* @providedPropType {array.<{children: object, count: number, isRefined: boolean, label: string, value: string}>} items - the list of items the HierarchicalMenu can display. Children has the same shape as parent items.
@@ -204,12 +204,14 @@ export default createConnector({
const currentRefinement = getCurrentRefinement(props, state);
return {
id,
- filters: !currentRefinement ? [] : [{
+ items: !currentRefinement ? [] : [{
label: `${id}: ${currentRefinement}`,
- clear: nextState => ({
+ attributeName: id,
+ value: nextState => ({
...nextState,
[id]: '',
}),
+ currentRefinement,
}],
};
},
diff --git a/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.test.js b/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.test.js
index c7846e66e6..530511955d 100644
--- a/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.test.js
+++ b/packages/react-instantsearch/src/connectors/connectHierarchicalMenu.test.js
@@ -196,21 +196,23 @@ describe('connectHierarchicalMenu', () => {
it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
- expect(metadata).toEqual({id: 'ok', filters: []});
+ expect(metadata).toEqual({items: [], id: 'ok'});
});
it('registers its filter in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {ok: 'wat'});
expect(metadata).toEqual({
id: 'ok',
- filters: [{
+ items: [{
label: 'ok: wat',
+ attributeName: 'ok',
+ currentRefinement: 'wat',
// Ignore clear, we test it later
- clear: metadata.filters[0].clear,
+ value: metadata.items[0].value,
}],
});
- const state = metadata.filters[0].clear({ok: 'wat'});
+ const state = metadata.items[0].value({ok: 'wat'});
expect(state).toEqual({ok: ''});
});
});
diff --git a/packages/react-instantsearch/src/connectors/connectMenu.js b/packages/react-instantsearch/src/connectors/connectMenu.js
index 7c5487f597..2199f2e47e 100644
--- a/packages/react-instantsearch/src/connectors/connectMenu.js
+++ b/packages/react-instantsearch/src/connectors/connectMenu.js
@@ -33,7 +33,7 @@ function getCurrentRefinement(props, state) {
* @propType {number} [limitMax=20] - the maximun number of displayed items. Only used when showMore is set to `true`
* @propType {string[]} [sortBy=['count:desc','name:asc']] - defines how the items are sorted. See [the helper documentation](https://community.algolia.com/algoliasearch-helper-js/reference.html#specifying-a-different-sort-order-for-values) for the full list of options
* @propType {string} defaultRefinement - the value of the item selected by default
- * @providedPropType {function} refine - a function to remove a single filter
+ * @providedPropType {function} refine - a function to toggle a refinement
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied
* @providedPropType {array.<{count: number, isRefined: boolean, label: string, value: string}>} items - the list of items the Menu can display.
@@ -124,12 +124,14 @@ export default createConnector({
const currentRefinement = getCurrentRefinement(props, state);
return {
id,
- filters: currentRefinement === null ? [] : [{
+ items: currentRefinement === null ? [] : [{
label: `${props.attributeName}: ${currentRefinement}`,
- clear: nextState => ({
+ attributeName: props.attributeName,
+ value: nextState => ({
...nextState,
[id]: '',
}),
+ currentRefinement,
}],
};
},
diff --git a/packages/react-instantsearch/src/connectors/connectMenu.test.js b/packages/react-instantsearch/src/connectors/connectMenu.test.js
index 4b27775a59..f5d24f4413 100644
--- a/packages/react-instantsearch/src/connectors/connectMenu.test.js
+++ b/packages/react-instantsearch/src/connectors/connectMenu.test.js
@@ -150,21 +150,23 @@ describe('connectMenu', () => {
it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
- expect(metadata).toEqual({id: 'ok', filters: []});
+ expect(metadata).toEqual({id: 'ok', items: []});
});
it('registers its filter in metadata', () => {
const metadata = getMetadata({id: 'ok', attributeName: 'wot'}, {ok: 'wat'});
expect(metadata).toEqual({
id: 'ok',
- filters: [{
+ items: [{
label: 'wot: wat',
+ attributeName: 'wot',
+ currentRefinement: 'wat',
// Ignore clear, we test it later
- clear: metadata.filters[0].clear,
+ value: metadata.items[0].value,
}],
});
- const state = metadata.filters[0].clear({ok: 'wat'});
+ const state = metadata.items[0].value({ok: 'wat'});
expect(state).toEqual({ok: ''});
});
});
diff --git a/packages/react-instantsearch/src/connectors/connectMultiRange.js b/packages/react-instantsearch/src/connectors/connectMultiRange.js
index bf6b64b9fd..19eaace7f5 100644
--- a/packages/react-instantsearch/src/connectors/connectMultiRange.js
+++ b/packages/react-instantsearch/src/connectors/connectMultiRange.js
@@ -47,7 +47,7 @@ function getCurrentRefinement(props, state) {
* @propType {string} attributeName - the name of the attribute in the records
* @propType {{label: string, start: number, end: number}[]} items - List of options. With a text label, and upper and lower bounds.
* @propType {string} defaultRefinement - the value of the item selected by default, follow the shape of a `string` with a pattern of `'{start}:{end}'`.
- * @providedPropType {function} refine - a function to remove a single filter
+ * @providedPropType {function} refine - a function to select a range.
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied. follow the shape of a `string` with a pattern of `'{start}:{end}'` which corresponds to the current selected item. For instance, when the selected item is `{start: 10, end: 20}`, the state of the widget is `'10:20'`. When `start` isn't defined, the state of the widget is `':{end}'`, and the same way around when `end` isn't defined. However, when neither `start` nor `end` are defined, the state is an empty string.
* @providedPropType {array.<{isRefined: boolean, label: string, value: string}>} items - the list of ranges the MultiRange can display.
@@ -113,17 +113,19 @@ export default createConnector({
getMetadata(props, state) {
const id = getId(props);
const value = getCurrentRefinement(props, state);
- const filters = [];
+ const items = [];
if (value !== '') {
const {label} = find(props.items, item => stringifyItem(item) === value);
- filters.push({
+ items.push({
label: `${props.attributeName}: ${label}`,
- clear: nextState => ({
+ attributeName: props.attributeName,
+ currentRefinement: label,
+ value: nextState => ({
...nextState,
[id]: '',
}),
});
}
- return {id, filters};
+ return {id, items};
},
});
diff --git a/packages/react-instantsearch/src/connectors/connectMultiRange.test.js b/packages/react-instantsearch/src/connectors/connectMultiRange.test.js
index 1d68692a2b..a1b61636f1 100644
--- a/packages/react-instantsearch/src/connectors/connectMultiRange.test.js
+++ b/packages/react-instantsearch/src/connectors/connectMultiRange.test.js
@@ -118,7 +118,7 @@ describe('connectMultiRange', () => {
it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
- expect(metadata).toEqual({id: 'ok', filters: []});
+ expect(metadata).toEqual({id: 'ok', items: []});
});
it('registers its filter in metadata', () => {
@@ -135,14 +135,16 @@ describe('connectMultiRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
- filters: [{
+ items: [{
label: 'wot: YAY',
// Ignore clear, we test it later
- clear: metadata.filters[0].clear,
+ value: metadata.items[0].value,
+ attributeName: 'wot',
+ currentRefinement: 'YAY',
}],
});
- const state = metadata.filters[0].clear({wot: '100:200'});
+ const state = metadata.items[0].value({wot: '100:200'});
expect(state).toEqual({wot: ''});
});
});
diff --git a/packages/react-instantsearch/src/connectors/connectRange.js b/packages/react-instantsearch/src/connectors/connectRange.js
index f17cb718ac..f6ea85c9be 100644
--- a/packages/react-instantsearch/src/connectors/connectRange.js
+++ b/packages/react-instantsearch/src/connectors/connectRange.js
@@ -14,7 +14,7 @@ import createConnector from '../core/createConnector';
* @propType {{min: number, max: number}} defaultRefinement - Default state of the widget containing the start and the end of the range.
* @propType {number} min - Minimum value. When this isn't set, the minimum value will be automatically computed by Algolia using the data in the index.
* @propType {number} max - Maximum value. When this isn't set, the maximum value will be automatically computed by Algolia using the data in the index.
- * @providedPropType {function} refine - a function to remove a single filter
+ * @providedPropType {function} refine - a function to select a range.
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied
*/
@@ -125,21 +125,23 @@ export default createConnector({
getMetadata(props, state) {
const id = getId(props);
const currentRefinement = getCurrentRefinement(props, state);
- let filter;
+ let item;
const hasMin = typeof currentRefinement.min !== 'undefined';
const hasMax = typeof currentRefinement.max !== 'undefined';
if (hasMin || hasMax) {
- let filterLabel = '';
+ let itemLabel = '';
if (hasMin) {
- filterLabel += `${currentRefinement.min} <= `;
+ itemLabel += `${currentRefinement.min} <= `;
}
- filterLabel += props.attributeName;
+ itemLabel += props.attributeName;
if (hasMax) {
- filterLabel += ` <= ${currentRefinement.max}`;
+ itemLabel += ` <= ${currentRefinement.max}`;
}
- filter = {
- label: filterLabel,
- clear: nextState => ({
+ item = {
+ label: itemLabel,
+ currentRefinement,
+ attributeName: props.attributeName,
+ value: nextState => ({
...nextState,
[id]: {},
}),
@@ -148,7 +150,7 @@ export default createConnector({
return {
id,
- filters: filter ? [filter] : [],
+ items: item ? [item] : [],
};
},
});
diff --git a/packages/react-instantsearch/src/connectors/connectRange.test.js b/packages/react-instantsearch/src/connectors/connectRange.test.js
index b3d7e0738a..86b6d6fe22 100644
--- a/packages/react-instantsearch/src/connectors/connectRange.test.js
+++ b/packages/react-instantsearch/src/connectors/connectRange.test.js
@@ -109,14 +109,16 @@ describe('connectRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
- filters: [{
+ items: [{
label: '5 <= wot',
+ attributeName: 'wot',
+ currentRefinement: {min: 5, max: undefined},
// Ignore clear, we test it later
- clear: metadata.filters[0].clear,
+ value: metadata.items[0].value,
}],
});
- const state = metadata.filters[0].clear({wot: {min: 5}});
+ const state = metadata.items[0].value({wot: {min: 5}});
expect(state).toEqual({wot: {}});
metadata = getMetadata(
@@ -125,9 +127,11 @@ describe('connectRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
- filters: [{
+ items: [{
label: 'wot <= 10',
- clear: metadata.filters[0].clear,
+ attributeName: 'wot',
+ currentRefinement: {min: undefined, max: 10},
+ value: metadata.items[0].value,
}],
});
@@ -137,9 +141,11 @@ describe('connectRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
- filters: [{
+ items: [{
label: '5 <= wot <= 10',
- clear: metadata.filters[0].clear,
+ attributeName: 'wot',
+ currentRefinement: {min: 5, max: 10},
+ value: metadata.items[0].value,
}],
});
});
diff --git a/packages/react-instantsearch/src/connectors/connectRefinementList.js b/packages/react-instantsearch/src/connectors/connectRefinementList.js
index 6017447c80..89de2a83ef 100644
--- a/packages/react-instantsearch/src/connectors/connectRefinementList.js
+++ b/packages/react-instantsearch/src/connectors/connectRefinementList.js
@@ -49,7 +49,7 @@ function getValue(name, props, state) {
* @propType {number} [limitMax=20] - the maximun number of displayed items. Only used when showMore is set to `true`
* @propType {string[]} [sortBy=['count:desc','name:asc']] - defines how the items are sorted. See [the helper documentation](https://community.algolia.com/algoliasearch-helper-js/reference.html#specifying-a-different-sort-order-for-values) for the full list of options
* @propType {string[]} defaultRefinement - the values of the items selected by default. The state of this widget takes the form of a list of `string`s, which correspond to the values of all selected refinements. However, when there are no refinements selected, the value of the state is an empty string.
- * @providedPropType {function} refine - a function to remove a single filter
+ * @providedPropType {function} refine - a function to toggle a refinement
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string[]} currentRefinement - the refinement currently applied
* @providedPropType {array.<{count: number, isRefined: boolean, label: string, value: string}>} items - the list of items the RefinementList can display.
@@ -139,26 +139,36 @@ export default createConnector({
searchParameters = searchParameters[addKey](attributeName);
return getCurrentRefinement(props, state).reduce((res, val) =>
- res[addRefinementKey](attributeName, val)
- , searchParameters);
+ res[addRefinementKey](attributeName, val)
+ , searchParameters);
},
getMetadata(props, state) {
const id = getId(props);
return {
id,
- filters: getCurrentRefinement(props, state).map(item => ({
- label: `${props.attributeName}: ${item}`,
- clear: nextState => {
- const nextSelectedItems = getCurrentRefinement(props, nextState).filter(
- other => other !== item
- );
- return {
- ...nextState,
- [id]: nextSelectedItems.length > 0 ? nextSelectedItems : '',
- };
- },
- })),
+ items: getCurrentRefinement(props, state).length > 0 ? [{
+ attributeName: props.attributeName,
+ label: `${props.attributeName}: `,
+ currentRefinement: getCurrentRefinement(props, state),
+ value: nextState => ({
+ ...nextState,
+ [id]: '',
+ }),
+ items: getCurrentRefinement(props, state).map(item => ({
+ label: `${item}`,
+ value: nextState => {
+ const nextSelectedItems = getCurrentRefinement(props, nextState).filter(
+ other => other !== item
+ );
+
+ return {
+ ...nextState,
+ [id]: nextSelectedItems.length > 0 ? nextSelectedItems : '',
+ };
+ },
+ })),
+ }] : [],
};
},
});
diff --git a/packages/react-instantsearch/src/connectors/connectRefinementList.test.js b/packages/react-instantsearch/src/connectors/connectRefinementList.test.js
index fa2d7a85e7..ab82caf41a 100644
--- a/packages/react-instantsearch/src/connectors/connectRefinementList.test.js
+++ b/packages/react-instantsearch/src/connectors/connectRefinementList.test.js
@@ -163,7 +163,7 @@ describe('connectRefinementList', () => {
it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
- expect(metadata).toEqual({id: 'ok', filters: []});
+ expect(metadata).toEqual({id: 'ok', items: []});
});
it('registers its filter in metadata', () => {
@@ -173,22 +173,30 @@ describe('connectRefinementList', () => {
);
expect(metadata).toEqual({
id: 'ok',
- filters: [
+ items: [
{
- label: 'wot: wat',
- // Ignore clear, we test it later
- clear: metadata.filters[0].clear,
- },
- {
- label: 'wot: wut',
- clear: metadata.filters[1].clear,
+ label: 'wot: ',
+ attributeName: 'wot',
+ currentRefinement: ['wat', 'wut'],
+ value: metadata.items[0].value,
+ items: [
+ {
+ label: 'wat',
+ value: metadata.items[0].items[0].value,
+ },
+ {
+ label: 'wut',
+ value: metadata.items[0].items[1].value,
+ },
+ ],
+ // Ignore value, we test it later
},
],
});
- let state = metadata.filters[0].clear({ok: ['wat', 'wut']});
+ let state = metadata.items[0].items[0].value({ok: ['wat', 'wut']});
expect(state).toEqual({ok: ['wut']});
- state = metadata.filters[1].clear(state);
+ state = metadata.items[0].items[1].value(state);
expect(state).toEqual({ok: ''});
});
});
diff --git a/packages/react-instantsearch/src/connectors/connectToggle.js b/packages/react-instantsearch/src/connectors/connectToggle.js
index 58db4880e6..a2a9ffbbaa 100644
--- a/packages/react-instantsearch/src/connectors/connectToggle.js
+++ b/packages/react-instantsearch/src/connectors/connectToggle.js
@@ -29,7 +29,7 @@ function getCurrentRefinement(props, state) {
* @propType {string} function - Custom filter. Takes in a `SearchParameters` and returns a new `SearchParameters` with the filter applied.
* @propType {string} value - Value of the refinement to apply on `attributeName`. Required when `attributeName` is present.
* @propType {boolean} [defaultChecked=false] - Default state of the widget. Should the toggle be checked by default?
- * @providedPropType {function} refine - a function to remove a single filter
+ * @providedPropType {function} refine - a function to toggle a refinement
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
*/
export default createConnector({
@@ -80,16 +80,18 @@ export default createConnector({
getMetadata(props, state) {
const id = getId(props);
const checked = getCurrentRefinement(props, state);
- const filters = [];
+ const items = [];
if (checked) {
- filters.push({
+ items.push({
label: props.label,
- clear: nextState => ({
+ currentRefinement: props.label,
+ attributeName: props.attributeName,
+ value: nextState => ({
...nextState,
[id]: 'off',
}),
});
}
- return {id, filters};
+ return {id, items};
},
});
diff --git a/packages/react-instantsearch/src/connectors/connectToggle.test.js b/packages/react-instantsearch/src/connectors/connectToggle.test.js
index 138ac32ee0..761c1ba8f9 100644
--- a/packages/react-instantsearch/src/connectors/connectToggle.test.js
+++ b/packages/react-instantsearch/src/connectors/connectToggle.test.js
@@ -61,23 +61,25 @@ describe('connectToggle', () => {
it('registers its filter in metadata', () => {
let metadata = getMetadata({id: 't'}, {});
expect(metadata).toEqual({
+ items: [],
id: 't',
- filters: [],
});
- metadata = getMetadata({id: 't', label: 'yep'}, {t: 'on'});
+ metadata = getMetadata({attributeName: 't', id: 't', label: 'yep'}, {t: 'on'});
expect(metadata).toEqual({
- id: 't',
- filters: [
+ items: [
{
label: 'yep',
// Ignore clear, we test it later
- clear: metadata.filters[0].clear,
+ value: metadata.items[0].value,
+ attributeName: 't',
+ currentRefinement: 'yep',
},
],
+ id: 't',
});
- const state = metadata.filters[0].clear({t: 'on'});
+ const state = metadata.items[0].value({t: 'on'});
expect(state).toEqual({t: 'off'});
});
});