diff --git a/dev/app/builtin/stories/menu.stories.js b/dev/app/builtin/stories/menu.stories.js
index 700917fbfa..1d5ed0dbb3 100644
--- a/dev/app/builtin/stories/menu.stories.js
+++ b/dev/app/builtin/stories/menu.stories.js
@@ -14,7 +14,7 @@ export default () => {
window.search.addWidget(
instantsearch.widgets.menu({
container,
- attributeName: 'categories',
+ attribute: 'categories',
})
);
})
@@ -25,7 +25,7 @@ export default () => {
window.search.addWidget(
instantsearch.widgets.menu({
container,
- attributeName: 'categories',
+ attribute: 'categories',
transformItems: items =>
items.map(item => ({
...item,
@@ -36,35 +36,33 @@ export default () => {
})
)
.add(
- 'with show more and header',
+ 'with show more',
wrapWithHits(container => {
window.search.addWidget(
instantsearch.widgets.menu({
container,
- attributeName: 'categories',
+ attribute: 'categories',
limit: 3,
- showMore: {
- templates: {
- active: '',
- inactive: '',
- },
- limit: 10,
- },
- templates: {
- header: 'Categories (menu widget)',
- },
+ showMore: true,
+ showMoreLimit: 10,
})
);
})
)
.add(
- 'as a Select DOM element',
+ 'with show more and templates',
wrapWithHits(container => {
window.search.addWidget(
- instantsearch.widgets.menuSelect({
+ instantsearch.widgets.menu({
container,
- attributeName: 'categories',
- limit: 10,
+ attribute: 'categories',
+ limit: 3,
+ showMore: true,
+ showMoreLimit: 10,
+ templates: {
+ showMoreActive: 'Show way less',
+ showMoreInactive: 'Show way more',
+ },
})
);
})
diff --git a/dev/app/jquery/stories/menu.stories.js b/dev/app/jquery/stories/menu.stories.js
index 4e73b49be9..48e373f86c 100644
--- a/dev/app/jquery/stories/menu.stories.js
+++ b/dev/app/jquery/stories/menu.stories.js
@@ -12,7 +12,7 @@ export default () => {
window.search.addWidget(
widgets.menu({
containerNode,
- attributeName: 'categories',
+ attribute: 'categories',
limit: 3,
})
);
@@ -24,7 +24,7 @@ export default () => {
window.search.addWidget(
widgets.showMoreMenu({
containerNode,
- attributeName: 'categories',
+ attribute: 'categories',
limit: 3,
showMoreLimit: 10,
})
diff --git a/docgen/src/guides/v3-migration.md b/docgen/src/guides/v3-migration.md
index f627b433f4..eb765c9815 100644
--- a/docgen/src/guides/v3-migration.md
+++ b/docgen/src/guides/v3-migration.md
@@ -278,6 +278,81 @@ With the redo button:
```
+### Menu
+
+#### Options
+
+| Before | After |
+| ----------------------------- | ---------------------------- |
+| `attributeName` | `attribute` |
+| `showMore.limit` | `showMoreLimit` |
+| `showMore.templates.active` | `templates.showMoreActive` |
+| `showMore.templates.inactive` | `templates.showMoreInactive` |
+
+- `showMore` is now a boolean option (`showMore.templates` are now in `templates`)
+- `sortBy` defaults to `['isRefined', 'name:asc']`
+
+#### CSS classes
+
+| Before | After |
+| ------------------------ | ----------------------------- |
+| `ais-menu` | `ais-Menu` |
+| `ais-menu--list` | `ais-Menu-list` |
+| `ais-menu--item` | `ais-Menu-item` |
+| `ais-menu--item__active` | `ais-Menu-item--selected` |
+| `ais-menu--link` | `ais-Menu-link` |
+| | `ais-Menu-label` |
+| `ais-menu--count` | `ais-Menu-count` |
+| | `ais-Menu-noResults` |
+| | `ais-Menu-showMore` |
+| | `ais-Menu-showMore--disabled` |
+
+#### Markup
+
+##### Default
+
+```html
+
+```
+
+##### Show more disabled
+
+```html
+
+```
+
### MenuSelect
#### Options
diff --git a/src/connectors/menu/connectMenu.js b/src/connectors/menu/connectMenu.js
index 36922cab59..df0d7861cb 100644
--- a/src/connectors/menu/connectMenu.js
+++ b/src/connectors/menu/connectMenu.js
@@ -17,8 +17,8 @@ search.addWidget(
customMenu({
attribute,
[ limit ],
- [ showMoreLimit ]
- [ sortBy = ['name:asc'] ]
+ [ showMoreLimit ],
+ [ sortBy = ['isRefined', 'name:asc'] ],
[ transformItems ]
})
);
@@ -37,8 +37,8 @@ Full documentation available at https://community.algolia.com/instantsearch.js/v
* @typedef {Object} CustomMenuWidgetOptions
* @property {string} attribute Name of the attribute for faceting (eg. "free_shipping").
* @property {number} [limit = 10] How many facets values to retrieve.
- * @property {number} [showMoreLimit = undefined] How many facets values to retrieve when `toggleShowMore` is called, this value is meant to be greater than `limit` option.
- * @property {string[]|function} [sortBy = ['name:asc']] How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.
+ * @property {number} [showMoreLimit = 10] How many facets values to retrieve when `toggleShowMore` is called, this value is meant to be greater than `limit` option.
+ * @property {string[]|function} [sortBy = ['isRefined', 'name:asc']] How to sort refinements. Possible values: `count|isRefined|name:asc|name:desc`.
*
* You can also use a sort function that behaves like the standard Javascript [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Syntax).
* @property {function(object[]):object[]} [transformItems] Function to transform the items passed to the templates.
@@ -113,8 +113,8 @@ export default function connectMenu(renderFn, unmountFn) {
const {
attribute,
limit = 10,
- sortBy = ['name:asc'],
- showMoreLimit,
+ sortBy = ['isRefined', 'name:asc'],
+ showMoreLimit = limit,
transformItems = items => items,
} = widgetParams;
diff --git a/src/widgets/menu/__tests__/__snapshots__/menu-test.js.snap b/src/widgets/menu/__tests__/__snapshots__/menu-test.js.snap
index 5137cde5cb..8be2964019 100644
--- a/src/widgets/menu/__tests__/__snapshots__/menu-test.js.snap
+++ b/src/widgets/menu/__tests__/__snapshots__/menu-test.js.snap
@@ -3,19 +3,19 @@
exports[`menu render renders transformed items 1`] = `
{{label}} {{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}",
+ "item": "{{label}}{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}",
+ "showMoreActive": "Show less",
+ "showMoreInactive": "Show more",
},
"templatesConfig": undefined,
"transformData": undefined,
"useCustomCompileOptions": Object {
- "footer": false,
- "header": false,
"item": false,
+ "showMoreActive": false,
+ "showMoreInactive": false,
},
}
}
@@ -62,19 +61,19 @@ exports[`menu render renders transformed items 1`] = `
exports[`menu render snapshot 1`] = `
{{label}} {{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}",
+ "item": "{{label}}{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}",
+ "showMoreActive": "Show less",
+ "showMoreInactive": "Show more",
},
"templatesConfig": undefined,
"transformData": undefined,
"useCustomCompileOptions": Object {
- "footer": false,
- "header": false,
"item": false,
+ "showMoreActive": false,
+ "showMoreInactive": false,
},
}
}
diff --git a/src/widgets/menu/__tests__/menu-test.js b/src/widgets/menu/__tests__/menu-test.js
index 17e78aeac8..716df658de 100644
--- a/src/widgets/menu/__tests__/menu-test.js
+++ b/src/widgets/menu/__tests__/menu-test.js
@@ -1,14 +1,37 @@
import menu from '../menu';
describe('menu', () => {
- it('throws an exception when no attributeName', () => {
+ it('throws an exception when no attribute', () => {
const container = document.createElement('div');
expect(menu.bind(null, { container })).toThrow(/^Usage/);
});
it('throws an exception when no container', () => {
- const attributeName = '';
- expect(menu.bind(null, { attributeName })).toThrow(/^Usage/);
+ expect(menu.bind(null, { attribute: '' })).toThrow(/^Usage/);
+ });
+
+ it('throws an exception when showMoreLimit without showMore option', () => {
+ const container = document.createElement('div');
+ expect(
+ menu.bind(null, { attribute: 'attribute', container, showMoreLimit: 10 })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\`showMoreLimit\` must be used with \`showMore\` set to \`true\`."`
+ );
+ });
+
+ it('throws an exception when showMoreLimit is lower than limit', () => {
+ const container = document.createElement('div');
+ expect(
+ menu.bind(null, {
+ attribute: 'attribute',
+ container,
+ limit: 20,
+ showMore: true,
+ showMoreLimit: 10,
+ })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"\`showMoreLimit\` should be greater than \`limit\`."`
+ );
});
describe('render', () => {
@@ -35,7 +58,7 @@ describe('menu', () => {
it('snapshot', () => {
const widget = menu({
container: document.createElement('div'),
- attributeName: 'test',
+ attribute: 'test',
});
widget.init({
@@ -51,7 +74,7 @@ describe('menu', () => {
it('renders transformed items', () => {
const widget = menu({
container: document.createElement('div'),
- attributeName: 'test',
+ attribute: 'test',
transformItems: items =>
items.map(item => ({ ...item, transformed: true })),
});
diff --git a/src/widgets/menu/defaultTemplates.js b/src/widgets/menu/defaultTemplates.js
index 64def3bcd6..e93aae4ed3 100644
--- a/src/widgets/menu/defaultTemplates.js
+++ b/src/widgets/menu/defaultTemplates.js
@@ -1,7 +1,10 @@
/* eslint-disable max-len */
export default {
- header: '',
item:
- '{{label}} {{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}',
- footer: '',
+ '' +
+ '{{label}}' +
+ '{{#helpers.formatNumber}}{{count}}{{/helpers.formatNumber}}' +
+ '',
+ showMoreActive: 'Show less',
+ showMoreInactive: 'Show more',
};
diff --git a/src/widgets/menu/menu.js b/src/widgets/menu/menu.js
index 62adcaca31..45cdb6b491 100644
--- a/src/widgets/menu/menu.js
+++ b/src/widgets/menu/menu.js
@@ -1,35 +1,24 @@
import React, { render, unmountComponentAtNode } from 'preact-compat';
import cx from 'classnames';
-
-import defaultTemplates from './defaultTemplates.js';
-import getShowMoreConfig from '../../lib/show-more/getShowMoreConfig.js';
-import connectMenu from '../../connectors/menu/connectMenu.js';
import RefinementList from '../../components/RefinementList/RefinementList.js';
+import connectMenu from '../../connectors/menu/connectMenu.js';
+import defaultTemplates from './defaultTemplates.js';
+import { prepareTemplateProps, getContainerNode } from '../../lib/utils.js';
+import { component } from '../../lib/suit';
-import {
- bemHelper,
- prepareTemplateProps,
- getContainerNode,
- prefixKeys,
-} from '../../lib/utils.js';
-
-const bem = bemHelper('ais-menu');
+const suit = component('Menu');
const renderer = ({
containerNode,
cssClasses,
- collapsible,
- autoHideContainer,
renderState,
templates,
- transformData,
- showMoreConfig,
+ showMore,
}) => (
{
refine,
items,
createURL,
- canRefine,
instantSearchInstance,
isShowingMore,
toggleShowMore,
@@ -39,7 +28,6 @@ const renderer = ({
) => {
if (isFirstRendering) {
renderState.templateProps = prepareTemplateProps({
- transformData,
defaultTemplates,
templatesConfig: instantSearchInstance.templatesConfig,
templates,
@@ -51,16 +39,13 @@ const renderer = ({
...facetValue,
url: createURL(facetValue.name),
}));
- const shouldAutoHideContainer = autoHideContainer && !canRefine;
render(
than the limit in the main configuration'); // eslint-disable-line
+ if (!showMore && showMoreLimit) {
+ throw new Error(
+ '`showMoreLimit` must be used with `showMore` set to `true`.'
+ );
}
- const containerNode = getContainerNode(container);
+ if (showMore && showMoreLimit < limit) {
+ throw new Error('`showMoreLimit` should be greater than `limit`.');
+ }
- const showMoreLimit = (showMoreConfig && showMoreConfig.limit) || undefined;
- const showMoreTemplates =
- showMoreConfig && prefixKeys('show-more-', showMoreConfig.templates);
- const allTemplates = showMoreTemplates
- ? { ...templates, ...showMoreTemplates }
- : templates;
+ const containerNode = getContainerNode(container);
const cssClasses = {
- root: cx(bem(null), userCssClasses.root),
- header: cx(bem('header'), userCssClasses.header),
- body: cx(bem('body'), userCssClasses.body),
- footer: cx(bem('footer'), userCssClasses.footer),
- list: cx(bem('list'), userCssClasses.list),
- item: cx(bem('item'), userCssClasses.item),
- active: cx(bem('item', 'active'), userCssClasses.active),
- link: cx(bem('link'), userCssClasses.link),
- count: cx(bem('count'), userCssClasses.count),
+ root: cx(suit(), userCssClasses.root),
+ noRefinementRoot: cx(
+ suit({ modifierName: 'noRefinement' }),
+ userCssClasses.noRefinementRoot
+ ),
+ list: cx(suit({ descendantName: 'list' }), userCssClasses.list),
+ item: cx(suit({ descendantName: 'item' }), userCssClasses.item),
+ selectedItem: cx(
+ suit({ descendantName: 'item', modifierName: 'selected' }),
+ userCssClasses.selectedItem
+ ),
+ link: cx(suit({ descendantName: 'link' }), userCssClasses.link),
+ label: cx(suit({ descendantName: 'label' }), userCssClasses.label),
+ count: cx(suit({ descendantName: 'count' }), userCssClasses.count),
+ showMore: cx(suit({ descendantName: 'showMore' }), userCssClasses.showMore),
+ disabledShowMore: cx(
+ suit({ descendantName: 'showMore', modifierName: 'disabled' }),
+ userCssClasses.disabledShowMore
+ ),
};
const specializedRenderer = renderer({
containerNode,
cssClasses,
- collapsible,
- autoHideContainer,
renderState: {},
- templates: allTemplates,
- transformData,
- showMoreConfig,
+ templates,
+ showMore,
});
try {
@@ -224,13 +189,13 @@ export default function menu({
unmountComponentAtNode(containerNode)
);
return makeWidget({
- attribute: attributeName,
+ attribute,
limit,
sortBy,
showMoreLimit,
transformItems,
});
- } catch (e) {
+ } catch (error) {
throw new Error(usage);
}
}