This repository has been archived by the owner on Dec 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 386
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(MenuSelect): add component and connector
* feat(components): add <MenuSelect /> * feat(widgets): add MenuSelect * feat(dom): export MenuSelect widget * fix(MenuSelect): use another value for "See all" option * doc(storybook): add MenuSelect stories * test(MenuSelect): update snapshot * doc(MenuSelect): remove className root not existing * style(MenuSelect): apply select theme * test(MenuSelect): remove un-used import * doc(MenuSelect): re-write classes description
- Loading branch information
Showing
8 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
packages/react-instantsearch-theme-algolia/styles/_MenuSelect.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.ais-MenuSelect__select { | ||
@include select(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import React, { Component } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { find } from 'lodash'; | ||
|
||
import classNames from './classNames.js'; | ||
import translatable from '../core/translatable'; | ||
|
||
const cx = classNames('MenuSelect'); | ||
|
||
class MenuSelect extends Component { | ||
static propTypes = { | ||
canRefine: PropTypes.bool.isRequired, | ||
refine: PropTypes.func.isRequired, | ||
translate: PropTypes.func.isRequired, | ||
items: PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
label: PropTypes.string.isRequired, | ||
value: PropTypes.string.isRequired, | ||
count: PropTypes.number.isRequired, | ||
isRefined: PropTypes.bool.isRequired, | ||
}) | ||
), | ||
}; | ||
|
||
static contextTypes = { | ||
canRefine: PropTypes.func, | ||
}; | ||
|
||
get selectedValue() { | ||
const { value } = find(this.props.items, { isRefined: true }) || { | ||
value: 'ais__see__all__option', | ||
}; | ||
return value; | ||
} | ||
|
||
componentWillMount() { | ||
if (this.context.canRefine) this.context.canRefine(this.props.canRefine); | ||
} | ||
|
||
componentWillReceiveProps(props) { | ||
if (this.context.canRefine) this.context.canRefine(props.canRefine); | ||
} | ||
|
||
handleSelectChange = ({ target: { value } }) => { | ||
this.props.refine(value === 'ais__see__all__option' ? '' : value); | ||
}; | ||
|
||
render() { | ||
const { items, translate } = this.props; | ||
|
||
return ( | ||
<select | ||
value={this.selectedValue} | ||
onChange={this.handleSelectChange} | ||
{...cx('select')} | ||
> | ||
<option value="ais__see__all__option" {...cx('option')}> | ||
{translate('seeAllOption')} | ||
</option> | ||
|
||
{items.map(item => ( | ||
<option key={item.value} value={item.value} {...cx('option')}> | ||
{item.label} ({item.count}) | ||
</option> | ||
))} | ||
</select> | ||
); | ||
} | ||
} | ||
|
||
export default translatable({ | ||
seeAllOption: 'See all', | ||
})(MenuSelect); |
79 changes: 79 additions & 0 deletions
79
packages/react-instantsearch/src/components/MenuSelect.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* eslint-env jest, jasmine */ | ||
|
||
import React from 'react'; | ||
import renderer from 'react-test-renderer'; | ||
import Enzyme, { mount } from 'enzyme'; | ||
import Adapter from 'enzyme-adapter-react-16'; | ||
Enzyme.configure({ adapter: new Adapter() }); | ||
|
||
import MenuSelect from './MenuSelect'; | ||
|
||
describe('MenuSelect', () => { | ||
it('default menu select', () => { | ||
const tree = renderer | ||
.create( | ||
<MenuSelect | ||
refine={() => {}} | ||
items={[ | ||
{ label: 'white', value: 'white', count: 10, isRefined: false }, | ||
{ label: 'black', value: 'black', count: 20, isRefined: false }, | ||
{ label: 'blue', value: 'blue', count: 30, isRefined: false }, | ||
{ label: 'green', value: 'green', count: 30, isRefined: false }, | ||
{ label: 'red', value: 'red', count: 30, isRefined: false }, | ||
]} | ||
canRefine={true} | ||
/> | ||
) | ||
.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
it('applies translations', () => { | ||
const tree = renderer | ||
.create( | ||
<MenuSelect | ||
refine={() => {}} | ||
items={[ | ||
{ label: 'white', value: 'white', count: 10, isRefined: false }, | ||
{ label: 'black', value: 'black', count: 20, isRefined: false }, | ||
{ label: 'blue', value: 'blue', count: 30, isRefined: false }, | ||
{ label: 'green', value: 'green', count: 30, isRefined: false }, | ||
{ label: 'red', value: 'red', count: 30, isRefined: false }, | ||
]} | ||
translations={{ | ||
seeAllOption: 'Everything', | ||
}} | ||
canRefine={true} | ||
/> | ||
) | ||
.toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
it('refines its value on change', () => { | ||
const refine = jest.fn(); | ||
const wrapper = mount( | ||
<MenuSelect | ||
refine={refine} | ||
items={[ | ||
{ label: 'white', value: 'white', count: 10, isRefined: false }, | ||
{ label: 'black', value: 'black', count: 20, isRefined: false }, | ||
{ label: 'blue', value: 'blue', count: 30, isRefined: false }, | ||
]} | ||
canRefine={true} | ||
/> | ||
); | ||
|
||
const items = wrapper.find('.ais-MenuSelect__option'); | ||
expect(items.length).toBe(4); // +1 from "see all option" | ||
|
||
wrapper | ||
.find('.ais-MenuSelect__select') | ||
.simulate('change', { target: { value: 'blue' } }); | ||
|
||
expect(refine).toHaveBeenCalledTimes(1); | ||
expect(refine).toHaveBeenCalledWith('blue'); | ||
|
||
wrapper.unmount(); | ||
}); | ||
}); |
121 changes: 121 additions & 0 deletions
121
packages/react-instantsearch/src/components/__snapshots__/MenuSelect.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`MenuSelect applies translations 1`] = ` | ||
<select | ||
className="ais-MenuSelect__select" | ||
onChange={[Function]} | ||
value="ais__see__all__option" | ||
> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="ais__see__all__option" | ||
> | ||
Everything | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="white" | ||
> | ||
white | ||
( | ||
10 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="black" | ||
> | ||
black | ||
( | ||
20 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="blue" | ||
> | ||
blue | ||
( | ||
30 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="green" | ||
> | ||
green | ||
( | ||
30 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="red" | ||
> | ||
red | ||
( | ||
30 | ||
) | ||
</option> | ||
</select> | ||
`; | ||
|
||
exports[`MenuSelect default menu select 1`] = ` | ||
<select | ||
className="ais-MenuSelect__select" | ||
onChange={[Function]} | ||
value="ais__see__all__option" | ||
> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="ais__see__all__option" | ||
> | ||
See all | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="white" | ||
> | ||
white | ||
( | ||
10 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="black" | ||
> | ||
black | ||
( | ||
20 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="blue" | ||
> | ||
blue | ||
( | ||
30 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="green" | ||
> | ||
green | ||
( | ||
30 | ||
) | ||
</option> | ||
<option | ||
className="ais-MenuSelect__option" | ||
value="red" | ||
> | ||
red | ||
( | ||
30 | ||
) | ||
</option> | ||
</select> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import connectMenu from '../connectors/connectMenu.js'; | ||
import MenuSelectComponent from '../components/MenuSelect.js'; | ||
|
||
/** | ||
* The MenuSelect component displays a select that lets the user choose a single value for a specific attribute. | ||
* @name MenuSelect | ||
* @kind widget | ||
* @requirements The attribute passed to the `attributeName` prop must be present in "attributes for faceting" | ||
* on the Algolia dashboard or configured as `attributesForFaceting` via a set settings call to the Algolia API. | ||
* @propType {string} attributeName - the name of the attribute in the record | ||
* @propType {string} [defaultRefinement] - the value of the item selected by default | ||
* @propType {function} [transformItems] - Function to modify the items being displayed, e.g. for filtering or sorting them. Takes an items as parameter and expects it back in return. | ||
* @themeKey ais-MenuSelect__select - the <select> DOM element. | ||
* @themeKey ais-MenuSelect__option - the <option> DOM element for a single item | ||
* @translationkey seeAllOption - The label of the option to select to remove the refinement | ||
* @example | ||
* import React from 'react'; | ||
* | ||
* import { MenuSelect, InstantSearch } from 'react-instantsearch/dom'; | ||
* | ||
* export default function App() { | ||
* return ( | ||
* <InstantSearch | ||
* appId="latency" | ||
* apiKey="6be0576ff61c053d5f9a3225e2a90f76" | ||
* indexName="ikea" | ||
* > | ||
* <MenuSelect | ||
* attributeName="category" | ||
* /> | ||
* </InstantSearch> | ||
* ); | ||
* } | ||
*/ | ||
export default connectMenu(MenuSelectComponent); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React from 'react'; | ||
import { orderBy } from 'lodash'; | ||
import { storiesOf } from '@storybook/react'; | ||
import { withKnobs, text } from '@storybook/addon-knobs'; | ||
|
||
import { WrapWithHits } from './util'; | ||
import { | ||
MenuSelect, | ||
Panel, | ||
SearchBox, | ||
} from '../packages/react-instantsearch/dom'; | ||
|
||
const stories = storiesOf('MenuSelect', module); | ||
|
||
stories.addDecorator(withKnobs); | ||
|
||
stories | ||
.add('default', () => ( | ||
<WrapWithHits hasPlayground={true} linkedStoryGroup="MenuSelect"> | ||
<MenuSelect attributeName="category" /> | ||
</WrapWithHits> | ||
)) | ||
.add('with default selected item', () => ( | ||
<WrapWithHits hasPlayground={true} linkedStoryGroup="MenuSelect"> | ||
<MenuSelect attributeName="category" defaultRefinement="Eating" /> | ||
</WrapWithHits> | ||
)) | ||
.add('with the sort strategy changed', () => ( | ||
<WrapWithHits hasPlayground={true} linkedStoryGroup="MenuSelect"> | ||
<MenuSelect | ||
attributeName="category" | ||
transformItems={items => | ||
orderBy(items, ['label', 'count'], ['asc', 'desc'])} | ||
/> | ||
</WrapWithHits> | ||
)) | ||
.add('with panel', () => ( | ||
<WrapWithHits hasPlayground={true} linkedStoryGroup="MenuSelect"> | ||
<Panel title="Category"> | ||
<MenuSelect attributeName="category" /> | ||
</Panel> | ||
</WrapWithHits> | ||
)) | ||
.add('with panel but no available refinement', () => ( | ||
<WrapWithHits | ||
searchBox={false} | ||
hasPlayground={true} | ||
linkedStoryGroup="MenuSelect" | ||
> | ||
<Panel title="Category"> | ||
<MenuSelect attributeName="category" /> | ||
<div style={{ display: 'none' }}> | ||
<SearchBox defaultRefinement="dkjsakdjskajdksjakdjaskj" /> | ||
</div> | ||
</Panel> | ||
</WrapWithHits> | ||
)) | ||
.add('playground', () => ( | ||
<WrapWithHits linkedStoryGroup="MenuSelect"> | ||
<MenuSelect | ||
attributeName="category" | ||
defaultRefinement={text('defaultSelectedItem', 'Bathroom')} | ||
/> | ||
</WrapWithHits> | ||
)); |