diff --git a/src/components/Search/Search-story.js b/src/components/Search/Search-story.js
index bf604b6caa79..4b3e16041b53 100644
--- a/src/components/Search/Search-story.js
+++ b/src/components/Search/Search-story.js
@@ -4,6 +4,7 @@ import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Search from '../Search';
+import SearchFilterButton from '../SearchFilterButton';
const searchProps = {
className: 'some-class',
@@ -87,4 +88,24 @@ storiesOf('Search', module)
return ;
}
+ )
+ .addWithInfo(
+ 'Custom set of buttons',
+ `
+ You can control what set of buttons you want.
+ `,
+ () => (
+ {
+ console.log('onChange');
+ action('onChange');
+ }}>
+
+
+ )
);
diff --git a/src/components/Search/Search-test.js b/src/components/Search/Search-test.js
index d12168704ad4..09e18af0722d 100644
--- a/src/components/Search/Search-test.js
+++ b/src/components/Search/Search-test.js
@@ -120,12 +120,6 @@ describe('Search', () => {
const icon = wrapper.find(Icon).at(3);
expect(icon.props().name).toEqual('list');
});
-
- it('should use "grid" icon when format state is not "list"', () => {
- wrapper.setState({ format: 'not-list' });
- const icon = wrapper.find(Icon).at(3);
- expect(icon.props().name).toEqual('grid');
- });
});
});
diff --git a/src/components/Search/Search.js b/src/components/Search/Search.js
index 2f9a1b66df77..674d69c08ab2 100644
--- a/src/components/Search/Search.js
+++ b/src/components/Search/Search.js
@@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import classNames from 'classnames';
import Icon from '../Icon';
+import SearchFilterButton from '../SearchFilterButton';
+import SearchLayoutButton from '../SearchLayoutButton';
export default class Search extends Component {
static propTypes = {
@@ -24,7 +26,6 @@ export default class Search extends Component {
};
state = {
- format: 'list',
hasContent: this.props.value || this.props.defaultValue || false,
};
@@ -44,18 +45,6 @@ export default class Search extends Component {
this.setState({ hasContent: false }, () => this.input.focus());
};
- toggleLayout = () => {
- if (this.state.format === 'list') {
- this.setState({
- format: 'grid',
- });
- } else {
- this.setState({
- format: 'list',
- });
- }
- };
-
handleChange = evt => {
this.setState({
hasContent: evt.target.value !== '',
@@ -64,55 +53,6 @@ export default class Search extends Component {
this.props.onChange(evt);
};
- // eslint-disable-next-line consistent-return
- searchFilterBtn = () => {
- if (!this.props.small) {
- return (
-
- );
- }
- };
-
- // eslint-disable-next-line consistent-return
- searchLayoutBtn = () => {
- if (!this.props.small) {
- return (
-
- );
- }
- };
-
render() {
const {
className,
@@ -124,7 +64,10 @@ export default class Search extends Component {
.substr(2)}`),
placeHolderText,
labelText,
+ searchButtonLabelText,
+ layoutButtonLabelText,
small,
+ children,
...other
} = this.props;
@@ -142,6 +85,8 @@ export default class Search extends Component {
'bx--search-close--hidden': !hasContent,
});
+ const renderButtons = !children && !small;
+
return (
- {this.searchFilterBtn()}
- {this.searchLayoutBtn()}
+ {children}
+ {renderButtons && (
+
+ )}
+ {renderButtons && (
+
+ )}
);
}
diff --git a/src/components/SearchFilterButton/SearchFilterButton-test.js b/src/components/SearchFilterButton/SearchFilterButton-test.js
new file mode 100644
index 000000000000..dd672914ee6f
--- /dev/null
+++ b/src/components/SearchFilterButton/SearchFilterButton-test.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import Icon from '../Icon';
+import SearchFilterButton from '../SearchFilterButton';
+import { mount } from 'enzyme';
+
+describe('SearchFilterButton', () => {
+ const wrapper = mount();
+
+ describe('buttons', () => {
+ const btn = wrapper.find('button');
+
+ it('should have type="button"', () => {
+ const type = btn.instance().getAttribute('type');
+ expect(type).toEqual('button');
+ });
+
+ it('has expected class', () => {
+ expect(btn.hasClass('bx--search-button')).toEqual(true);
+ });
+ });
+
+ describe('icons', () => {
+ it('should use "filter--glyph" icon', () => {
+ const icon = wrapper.find(Icon);
+ expect(icon.props().name).toEqual('filter--glyph');
+ });
+ });
+});
diff --git a/src/components/SearchFilterButton/SearchFilterButton.js b/src/components/SearchFilterButton/SearchFilterButton.js
new file mode 100644
index 000000000000..25c19412d860
--- /dev/null
+++ b/src/components/SearchFilterButton/SearchFilterButton.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Icon from '../Icon';
+
+/**
+ * The filter button for ``.
+ */
+const SearchFilterButton = ({ labelText }) => (
+
+);
+
+SearchFilterButton.propTypes = {
+ /**
+ * The a11y label text.
+ */
+ labelText: PropTypes.string,
+};
+
+SearchFilterButton.defaultProps = {
+ labelText: 'Search',
+};
+
+export default SearchFilterButton;
diff --git a/src/components/SearchFilterButton/index.js b/src/components/SearchFilterButton/index.js
new file mode 100644
index 000000000000..e09592fdebcd
--- /dev/null
+++ b/src/components/SearchFilterButton/index.js
@@ -0,0 +1 @@
+export default from './SearchFilterButton';
diff --git a/src/components/SearchLayoutButton/SearchLayoutButton-test.js b/src/components/SearchLayoutButton/SearchLayoutButton-test.js
new file mode 100644
index 000000000000..00fb11926759
--- /dev/null
+++ b/src/components/SearchLayoutButton/SearchLayoutButton-test.js
@@ -0,0 +1,56 @@
+import React from 'react';
+import Icon from '../Icon';
+import SearchLayoutButton from '../SearchLayoutButton';
+import { mount } from 'enzyme';
+
+describe('SearchLayoutButton', () => {
+ const wrapper = mount();
+
+ describe('buttons', () => {
+ const btn = wrapper.find('button');
+
+ it('should have type="button"', () => {
+ const type = btn.instance().getAttribute('type');
+ expect(type).toEqual('button');
+ });
+
+ it('has expected class for sort button', () => {
+ expect(btn.hasClass('bx--search-button')).toEqual(true);
+ });
+ });
+
+ describe('icons', () => {
+ it('should use "list" icon for toggle button', () => {
+ const icon = wrapper.find(Icon);
+ expect(icon.props().name).toEqual('list');
+ });
+
+ it('should use "grid" icon when format state is not "list"', () => {
+ wrapper.setState({ format: 'not-list' });
+ const icon = wrapper.find(Icon);
+ expect(icon.props().name).toEqual('grid');
+ });
+
+ it('should support specifying the layout via props', () => {
+ const wrapperWithFormatProps = mount(
+
+ );
+ expect(wrapperWithFormatProps.find(Icon).props().name).toEqual('grid');
+ wrapperWithFormatProps.setProps({ format: 'list' });
+ expect(wrapperWithFormatProps.find(Icon).props().name).toEqual('list');
+ });
+
+ it('should support being notified of change in layout', () => {
+ const onChangeFormat = jest.fn();
+ const wrapperWithFormatProps = mount(
+
+ );
+ wrapperWithFormatProps.find('button').simulate('click');
+ wrapperWithFormatProps.find('button').simulate('click');
+ expect(onChangeFormat.mock.calls).toEqual([
+ [{ format: 'list' }],
+ [{ format: 'grid' }],
+ ]);
+ });
+ });
+});
diff --git a/src/components/SearchLayoutButton/SearchLayoutButton.js b/src/components/SearchLayoutButton/SearchLayoutButton.js
new file mode 100644
index 000000000000..73e3945b8cb3
--- /dev/null
+++ b/src/components/SearchLayoutButton/SearchLayoutButton.js
@@ -0,0 +1,84 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import Icon from '../Icon';
+
+/**
+ * The layout button for ``.
+ */
+class SearchLayoutButton extends Component {
+ static propTypes = {
+ /**
+ * The layout.
+ */
+ format: PropTypes.oneOf(['list', 'grid']),
+
+ /**
+ * The a11y label text.
+ */
+ labelText: PropTypes.string,
+
+ /**
+ * The callback called when layout switches.
+ */
+ onChangeFormat: PropTypes.func,
+ };
+
+ static defaultProps = {
+ labelText: 'Filter',
+ };
+
+ state = {
+ /**
+ * The current layout.
+ * @type {string}
+ */
+ format: this.props.format || 'list',
+ };
+
+ componentWillReceiveProps({ format }) {
+ const { format: prevFormat } = this.props;
+ if (prevFormat !== format) {
+ this.setState({ format: format || 'list' });
+ }
+ }
+
+ /**
+ * Toggles the button state upon user-initiated event.
+ */
+ toggleLayout = () => {
+ const format = this.state.format === 'list' ? 'grid' : 'list';
+ this.setState({ format }, () => {
+ const { onChangeFormat } = this.props;
+ if (typeof onChangeFormat === 'function') {
+ onChangeFormat({ format });
+ }
+ });
+ };
+
+ render() {
+ const { labelText } = this.props;
+ return (
+
+ );
+ }
+}
+
+export default SearchLayoutButton;
diff --git a/src/components/SearchLayoutButton/index.js b/src/components/SearchLayoutButton/index.js
new file mode 100644
index 000000000000..3f2269357422
--- /dev/null
+++ b/src/components/SearchLayoutButton/index.js
@@ -0,0 +1 @@
+export default from './SearchLayoutButton';
diff --git a/src/index.js b/src/index.js
index db16ab726933..2ca06eac948e 100644
--- a/src/index.js
+++ b/src/index.js
@@ -76,6 +76,8 @@ export {
export RadioButton from './components/RadioButton';
export RadioButtonGroup from './components/RadioButtonGroup';
export Search from './components/Search';
+export SearchFilterButton from './components/SearchFilterButton';
+export SearchLayoutButton from './components/SearchLayoutButton';
export SecondaryButton from './components/SecondaryButton';
export Select from './components/Select';
export SelectItem from './components/SelectItem';