Skip to content

Commit 1f6a603

Browse files
feat(Filter): Add Filter Component
1 parent 00e9ad6 commit 1f6a603

File tree

12 files changed

+1741
-1
lines changed

12 files changed

+1741
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"homepage": "https://github.com/patternfly/patternfly-react#readme",
2020
"dependencies": {
2121
"classnames": "^2.2.5",
22-
"patternfly": "^3.31.0",
22+
"patternfly": "^3.35.0",
2323
"react-bootstrap": "^0.31.5",
2424
"react-c3js": "^0.1.20",
2525
"react-fontawesome": "^1.6.1",

src/components/Filter/Filter.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import cx from 'classnames';
2+
import React from 'react';
3+
import PropTypes from 'prop-types';
4+
5+
const Filter = ({ children, className, ...rest }) => {
6+
const classes = cx('filter-pf form-group', className);
7+
return (
8+
<div className={classes} {...rest}>
9+
<div className="filter-pf-fields">
10+
<div className="input-group">{children}</div>
11+
</div>
12+
</div>
13+
);
14+
};
15+
16+
Filter.propTypes = {
17+
/** Children nodes */
18+
children: PropTypes.node,
19+
/** Additional css classes */
20+
className: PropTypes.string
21+
};
22+
23+
export default Filter;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react';
2+
import { storiesOf } from '@storybook/react';
3+
import { defaultTemplate } from '../../../storybook/decorators/storyTemplates';
4+
import { withInfo } from '@storybook/addon-info/dist/index';
5+
import {
6+
Filter,
7+
FilterTypeSelector,
8+
FilterValueSelector,
9+
FilterCategorySelector,
10+
FilterCategoryValueSelector
11+
} from '../../index';
12+
13+
import {
14+
MockFilterExample,
15+
mockFilterExampleSource
16+
} from './__mocks__/mockFilterExample';
17+
18+
const stories = storiesOf('Filter', module);
19+
20+
stories.addDecorator(
21+
defaultTemplate({
22+
title: 'Filter',
23+
documentationLink:
24+
'http://www.patternfly.org/pattern-library/forms-and-controls/filter/'
25+
})
26+
);
27+
28+
stories.add(
29+
'Filter',
30+
withInfo({
31+
source: false,
32+
propTables: [
33+
Filter,
34+
FilterTypeSelector,
35+
FilterValueSelector,
36+
FilterCategorySelector,
37+
FilterCategoryValueSelector
38+
],
39+
propTablesExclude: [MockFilterExample],
40+
text: (
41+
<div>
42+
<h1>Story Source</h1>
43+
<pre>{mockFilterExampleSource}</pre>
44+
</div>
45+
)
46+
})(() => <MockFilterExample />)
47+
);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React from 'react';
2+
import renderer from 'react-test-renderer';
3+
import {
4+
Filter,
5+
FilterTypeSelector,
6+
FilterValueSelector,
7+
FilterCategorySelector,
8+
FilterCategoryValueSelector
9+
} from '../../index';
10+
import { mockFilterExampleFields } from './__mocks__/mockFilterExample';
11+
12+
test('Filter input renders properly', () => {
13+
const component = renderer.create(
14+
<Filter>
15+
<FilterTypeSelector
16+
filterTypes={mockFilterExampleFields}
17+
currentFilterType={mockFilterExampleFields[0]}
18+
/>
19+
<input
20+
className="form-control"
21+
type={mockFilterExampleFields[0].filterType}
22+
value=""
23+
placeholder="Filter by Name"
24+
/>
25+
</Filter>
26+
);
27+
28+
const tree = component.toJSON();
29+
expect(tree).toMatchSnapshot();
30+
});
31+
32+
test('Filter select renders properly', () => {
33+
const component = renderer.create(
34+
<Filter>
35+
<FilterTypeSelector
36+
filterTypes={mockFilterExampleFields}
37+
currentFilterType={mockFilterExampleFields[2]}
38+
/>
39+
<FilterValueSelector
40+
filterValues={mockFilterExampleFields[2].filterValues}
41+
currentValue={mockFilterExampleFields[2].filterValues[4]}
42+
/>
43+
</Filter>
44+
);
45+
46+
const tree = component.toJSON();
47+
expect(tree).toMatchSnapshot();
48+
});
49+
50+
test('Filter categories renders properly', () => {
51+
const component = renderer.create(
52+
<Filter>
53+
<FilterTypeSelector
54+
filterTypes={mockFilterExampleFields}
55+
currentFilterType={mockFilterExampleFields[3]}
56+
/>
57+
<FilterCategorySelector
58+
filterCategories={mockFilterExampleFields[3].filterCategories}
59+
currentCategory={mockFilterExampleFields[3].filterCategories[0]}
60+
placeholder={mockFilterExampleFields[3].placeholder}
61+
>
62+
<FilterCategoryValueSelector
63+
categoryValues={
64+
mockFilterExampleFields[3].filterCategories[0].filterValues
65+
}
66+
currentValue={
67+
mockFilterExampleFields[3].filterCategories[0].filterValues[0]
68+
}
69+
placeholder={mockFilterExampleFields[3].filterCategoriesPlaceholder}
70+
/>
71+
</FilterCategorySelector>
72+
</Filter>
73+
);
74+
75+
const tree = component.toJSON();
76+
expect(tree).toMatchSnapshot();
77+
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { DropdownButton } from '../Button';
4+
import { MenuItem } from '../MenuItem';
5+
import cx from 'classnames';
6+
7+
const FilterCategorySelector = ({
8+
children,
9+
className,
10+
id,
11+
filterCategories,
12+
currentCategory,
13+
placeholder,
14+
onFilterCategorySelected,
15+
...rest
16+
}) => {
17+
let classes = cx('filter-pf-category-select', className);
18+
19+
if (placeholder || (filterCategories && filterCategories.length > 1)) {
20+
let title;
21+
if (currentCategory) {
22+
title = currentCategory.title || currentCategory;
23+
} else {
24+
title = placeholder || filterCategories[0].title || filterCategories[0];
25+
}
26+
27+
let menuId = 'filterCategoryMenu';
28+
menuId += id ? `_${id}` : '';
29+
30+
return (
31+
<div className={classes} {...rest}>
32+
<div className="filter-pf-select">
33+
<DropdownButton
34+
title={title}
35+
id={menuId}
36+
className="filter-pf-select-dropdown"
37+
>
38+
{placeholder && (
39+
<MenuItem
40+
title={placeholder}
41+
key="Placeholder"
42+
onSelect={() =>
43+
onFilterCategorySelected && onFilterCategorySelected()
44+
}
45+
>
46+
{placeholder}
47+
</MenuItem>
48+
)}
49+
{filterCategories &&
50+
filterCategories.map((item, index) => {
51+
let classes = {
52+
selected: item === currentCategory
53+
};
54+
return (
55+
<MenuItem
56+
className={classes}
57+
key={item.id || index}
58+
onSelect={() =>
59+
onFilterCategorySelected && onFilterCategorySelected(item)
60+
}
61+
>
62+
{item.title || item}
63+
</MenuItem>
64+
);
65+
})}
66+
</DropdownButton>
67+
</div>
68+
{children}
69+
</div>
70+
);
71+
} else {
72+
return null;
73+
}
74+
};
75+
76+
FilterCategorySelector.propTypes = {
77+
/** Children nodes */
78+
children: PropTypes.node,
79+
/** Additional css classes */
80+
className: PropTypes.string,
81+
/** ID for the component, necessary for accessibility if there are multiple filters on a page */
82+
id: PropTypes.string,
83+
/** Array of filter categories, each can be a string or an object with a 'title' field */
84+
filterCategories: PropTypes.array.isRequired,
85+
/** Current selected category */
86+
currentCategory: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
87+
/** Placeholder text when no category is selected */
88+
placeholder: PropTypes.string,
89+
/** function(field, value) - Callback to call when a category is added */
90+
onFilterCategorySelected: PropTypes.func
91+
};
92+
93+
export default FilterCategorySelector;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { DropdownButton } from '../Button';
4+
import { MenuItem } from '../MenuItem';
5+
import cx from 'classnames';
6+
7+
const FilterCategoryValueSelector = ({
8+
className,
9+
id,
10+
categoryValues,
11+
currentValue,
12+
placeholder,
13+
onCategoryValueSelected,
14+
...rest
15+
}) => {
16+
let classes = cx('filter-pf-select', className);
17+
18+
if (placeholder || (categoryValues && categoryValues.length > 1)) {
19+
let title;
20+
if (currentValue) {
21+
title = currentValue.title || currentValue;
22+
} else {
23+
title = placeholder || categoryValues[0].title || categoryValues[0];
24+
}
25+
26+
let menuId = 'filterCategoryMenu';
27+
menuId += id ? `_${id}` : '';
28+
29+
return (
30+
<div className={classes} {...rest}>
31+
<DropdownButton
32+
className="filter-pf-category-select-value filter-pf-select-dropdown"
33+
title={title}
34+
id={menuId}
35+
>
36+
{placeholder && (
37+
<MenuItem
38+
title={placeholder}
39+
key="Placeholder"
40+
onSelect={() =>
41+
onCategoryValueSelected && onCategoryValueSelected()
42+
}
43+
>
44+
{placeholder}
45+
</MenuItem>
46+
)}
47+
{categoryValues &&
48+
categoryValues.map((item, index) => {
49+
let classes = {
50+
selected: item === currentValue
51+
};
52+
return (
53+
<MenuItem
54+
className={classes}
55+
key={item.id || index}
56+
onSelect={() =>
57+
onCategoryValueSelected && onCategoryValueSelected(item)
58+
}
59+
>
60+
{item.title || item}
61+
</MenuItem>
62+
);
63+
})}
64+
</DropdownButton>
65+
</div>
66+
);
67+
} else {
68+
return null;
69+
}
70+
};
71+
72+
FilterCategoryValueSelector.propTypes = {
73+
/** Additional css classes */
74+
className: PropTypes.string,
75+
/** ID for the filter component, necessary for accessibility if there are multiple filters on a page */
76+
id: PropTypes.string,
77+
/** Array of valid values for the category to select from, each can be a string or an object with a 'title' field */
78+
categoryValues: PropTypes.array,
79+
/** Currently selected category value */
80+
currentValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
81+
/** Placeholder text when no category value is selected */
82+
placeholder: PropTypes.string,
83+
/** function(field, value) - Callback to call when a category value is selected */
84+
onCategoryValueSelected: PropTypes.func
85+
};
86+
87+
export default FilterCategoryValueSelector;

0 commit comments

Comments
 (0)