Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
feat(Breadcrumb): add a new widget & connector (#228)
Browse files Browse the repository at this point in the history
* feat(Breadcrumb): create a story

* feat(Breadcrumb): retrieve items in Story

* feat(Breadcrumb): modified the getProvidedProps method in the connector

* feat(Breadcrumb): WIP

* feat(Breadcrumb): WIP

* feat(Breadcrumb): assembleBreadcrumb method

* feat(Breadcrumb): improve assembleBreadcrumb method

* feat(Breadcrumb): minor edits in connectBreadcrumb

* feat(Breadcrumb): added render method

* feat(Breadcrumb): Handled the separator prop

* feat(Breadcrumb): separator prop can be a string or a React Component

* feat(Breadcrumb): Added VirtualHierarchicalMenu + Refactored HierarchicalMenuLogic

* feat(Breadcrumb): Make tests pass!

* feat(Breadcrumb): Make component tests pass

* feat(Breadcrumb): disable click on the last item

* feat(Breadcrumb): added rootURL

* feat(Breadcrumb): add tests for rootURL

* feat(Breadcrumb): Added translation for rootURL + playground story

* feat(Breadcrumb): Added translation test

* feat(Breadcrumb): Added a story for the separator-custom element

* feat(Breadcrumb): Add custom React Component test for separator

* feat(Breadcrumb): Changed the translation name to "rootLabel"

* feat(Breadcrumb): Minor change in PropTypes

* feat(Breadcrumb): Add style

* feat(Breadcrumb): Minor modification to the playground story

* feat(Breadcrumb): Minor modifications to tests and style

* feat(Breadcrumb): review WIP

* feat(Breadcrumb): some cleanup

* feat(Breadcrumb): review

* feat(Breadcrumb): some fixes

* feat(Breadcrumb): remove hierarchicalMenuLogic file

* feat(Breadcrumb): modified the structure, putting the separator in between 2 elements

* Update style.scss

* feat(Breadcrumb): add snapshot

* feat(Breadcrumb): minor edits to doc and story + update yarn lock

* feat(Breadcrumb): changed div to span

* feat(Breadcrumb): changed to root div

* feat(Breadcrumb): update test

* feat(Breadcrumb): modify documentation
  • Loading branch information
marielaures authored and mthuret committed Aug 7, 2017
1 parent 7157bd8 commit 7f8f3ae
Show file tree
Hide file tree
Showing 13 changed files with 9,117 additions and 1,318 deletions.
1 change: 1 addition & 0 deletions packages/react-instantsearch-theme-algolia/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ $sffv-searchbox-config: (

@import 'mixins/searchbox.shipow',
'styles/InstantSearch',
'styles/Breadcrumb',
'styles/ClearAll',
'styles/CurrentRefinements',
'styles/HierarchicalMenu',
Expand Down
20 changes: 20 additions & 0 deletions packages/react-instantsearch-theme-algolia/styles/_Breadcrumb.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.ais-Breadcrumb__itemDisabled,
.ais-Breadcrumb__itemLabel,
.ais-Breadcrumb__separator,
.ais-Breadcrumb__rootLabel {
color: #3369e7;
}

.ais-Breadcrumb__itemLink {
font-weight: bold;
}

.ais-Breadcrumb__itemDisabled,
.ais-Breadcrumb__separator {
font-weight: normal;
}

.ais-Breadcrumb__item,
{
display: inline;
}
3 changes: 3 additions & 0 deletions packages/react-instantsearch/connectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export {
export {
default as connectScrollTo,
} from './src/connectors/connectScrollTo.js';
export {
default as connectBreadcrumb,
} from './src/connectors/connectBreadcrumb.js';
export {
default as connectSearchBox,
} from './src/connectors/connectSearchBox.js';
Expand Down
1 change: 1 addition & 0 deletions packages/react-instantsearch/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export { default as SortBy } from './src/widgets/SortBy.js';
export { default as Stats } from './src/widgets/Stats.js';
export { default as Toggle } from './src/widgets/Toggle.js';
export { default as Panel } from './src/widgets/Panel.js';
export { default as Breadcrumb } from './src/widgets/Breadcrumb';
95 changes: 95 additions & 0 deletions packages/react-instantsearch/src/components/Breadcrumb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Link from './Link';
import classNames from './classNames.js';
import translatable from '../core/translatable';

const cx = classNames('Breadcrumb');

const itemsPropType = PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
})
);

class Breadcrumb extends Component {
static propTypes = {
canRefine: PropTypes.bool.isRequired,
createURL: PropTypes.func.isRequired,
items: itemsPropType,
refine: PropTypes.func.isRequired,
rootURL: PropTypes.string,
separator: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
translate: PropTypes.func.isRequired,
};

static contextTypes = {
canRefine: PropTypes.func,
};

componentWillMount() {
if (this.context.canRefine) this.context.canRefine(this.props.canRefine);
}

componentWillReceiveProps(props) {
if (this.context.canRefine) this.context.canRefine(props.canRefine);
}

render() {
const { canRefine, createURL, items, refine, translate } = this.props;
const rootPath = canRefine
? <span {...cx('item')}>
<a
{...cx('itemLink', 'itemLinkRoot')}
onClick={() => (!this.props.rootURL ? refine() : null)}
href={this.props.rootURL ? this.props.rootURL : createURL()}
>
<span {...cx('rootLabel')}>
{translate('rootLabel')}
</span>
</a>
<span {...cx('separator')}>
{this.props.separator}
</span>
</span>
: null;

const breadcrumb = items.map((item, idx) => {
const isLast = idx === items.length - 1;
const separator = isLast ? '' : this.props.separator;
return !isLast
? <span {...cx('item')} key={idx}>
<Link
{...cx('itemLink')}
onClick={() => refine(item.value)}
href={createURL(item.value)}
key={idx}
>
<span {...cx('itemLabel')}>
{item.label}
</span>
</Link>
<span {...cx('separator')}>
{separator}
</span>
</span>
: <span {...cx('itemLink', 'itemDisabled', 'item')} key={idx}>
<span {...cx('itemLabel')}>
{item.label}
</span>
</span>;
});

return (
<div {...cx('root', !canRefine && 'noRefinement')}>
{rootPath}
{breadcrumb}
</div>
);
}
}

export default translatable({
rootLabel: 'Home',
})(Breadcrumb);
214 changes: 214 additions & 0 deletions packages/react-instantsearch/src/components/Breadcrumb.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import PropTypes from 'prop-types';
/* eslint-env jest, jasmine */

import React from 'react';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';

import Breadcrumb from './Breadcrumb';

describe('Breadcrumb', () => {
it('outputs the default breadcrumb', () => {
const tree = renderer
.create(
<Breadcrumb
refine={() => null}
createURL={() => '#'}
items={[
{
value: 'white',
label: 'white',
},
{
value: 'white > white1',
label: 'white1',
},
{
value: 'white > white1 > white1.1',
label: 'white1.1',
},
]}
canRefine={true}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

it('refines its value on change', () => {
const refine = jest.fn();
const wrapper = mount(
<Breadcrumb
refine={refine}
createURL={() => '#'}
items={[
{
value: 'white',
label: 'white',
},
{
value: 'white > white1',
label: 'white1',
},
{
value: 'white > white1 > white1.1',
label: 'white1.1',
},
]}
canRefine={true}
/>
);

const items = wrapper.find('.ais-Breadcrumb__itemLink');
expect(items.length).toBe(4);

items.first().simulate('click');
expect(refine.mock.calls.length).toBe(1);
expect(refine.mock.calls[0][0]).toEqual();

items.at(1).simulate('click');
expect(refine.mock.calls.length).toBe(2);
expect(refine.mock.calls[1][0]).toEqual('white');

items.at(2).simulate('click');
expect(refine.mock.calls.length).toBe(3);
expect(refine.mock.calls[2][0]).toEqual('white > white1');

items.at(3).simulate('click');
expect(refine.mock.calls.length).toBe(3);

wrapper.unmount();
});

it('has a rootURL prop', () => {
const refine = jest.fn();
const rootLink = 'www.algolia.com';

const wrapper = mount(
<Breadcrumb
refine={refine}
createURL={() => '#'}
rootURL={rootLink}
items={[
{
value: 'white',
label: 'white',
},
{
value: 'white > white1',
label: 'white1',
},
{
value: 'white > white1 > white1.1',
label: 'white1.1',
},
]}
canRefine={true}
/>
);

const items = wrapper.find('.ais-Breadcrumb__itemLink');
expect(items.length).toBe(4);

items.first().simulate('click');
expect(refine.mock.calls.length).toBe(0);
expect(wrapper.find('a').first().prop('href')).toEqual('www.algolia.com');

wrapper.unmount();
});

it('has a separator prop that can be a custom component', () => {
const tree = renderer
.create(
<Breadcrumb
refine={() => null}
createURL={() => '#'}
separator={<span>🔍</span>}
items={[
{
value: 'white',
label: 'white',
},
{
value: 'white > white1',
label: 'white1',
},
{
value: 'white > white1 > white1.1',
label: 'white1.1',
},
]}
canRefine={true}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

it('has customizable translations', () => {
const tree = renderer
.create(
<Breadcrumb
refine={() => null}
createURL={() => '#'}
translations={{
rootLabel: 'ROOT_LABEL',
}}
items={[
{
value: 'white',
label: 'white',
},
{
value: 'white > white1',
label: 'white1',
},
{
value: 'white > white1 > white1.1',
label: 'white1.1',
},
]}
canRefine={true}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

describe('Panel compatibility', () => {
it('Should indicate when there is no more refinement', () => {
const canRefine = jest.fn();
const wrapper = mount(
<Breadcrumb
refine={() => null}
createURL={() => '#'}
items={[
{
value: 'white',
label: 'white',
},
{
value: 'white > white1',
label: 'white1',
},
]}
canRefine={true}
/>,
{
context: { canRefine },
childContextTypes: { canRefine: PropTypes.func },
}
);

expect(canRefine.mock.calls.length).toBe(1);
expect(canRefine.mock.calls[0][0]).toEqual(true);
expect(wrapper.find('.ais-Breadcrumb__noRefinement').length).toBe(0);

wrapper.setProps({ canRefine: false });

expect(canRefine.mock.calls.length).toBe(2);
expect(canRefine.mock.calls[1][0]).toEqual(false);
expect(wrapper.find('.ais-Breadcrumb__noRefinement').length).toBe(1);
});
});
});
Loading

0 comments on commit 7f8f3ae

Please sign in to comment.