From 96adb227d98f6f8d2264071b493157e6e97d17a7 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 19 Aug 2018 20:52:33 +0200 Subject: [PATCH] Added search bar to tags list --- src/tags/TagsList.js | 20 +++++++++++++++----- src/tags/reducers/tagsList.js | 24 +++++++++++++++++++++--- src/utils/SearchField.js | 14 ++++++++++++-- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/tags/TagsList.js b/src/tags/TagsList.js index a8e641fd..5288a02b 100644 --- a/src/tags/TagsList.js +++ b/src/tags/TagsList.js @@ -1,11 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; import { pick, splitEvery } from 'ramda'; -import { listTags } from './reducers/tagsList'; +import { filterTags, listTags } from './reducers/tagsList'; import MuttedMessage from '../utils/MuttedMessage'; import TagCard from './TagCard'; +import SearchField from '../utils/SearchField'; -const { round } = Math; +const { ceil } = Math; export class TagsList extends React.Component { state = { isDeleteModalOpen: false }; @@ -29,12 +30,12 @@ export class TagsList extends React.Component { ); } - const tagsCount = tagsList.tags.length; + const tagsCount = tagsList.filteredTags.length; if (tagsCount < 1) { return No tags found; } - const tagsGroups = splitEvery(round(tagsCount / 4), tagsList.tags); + const tagsGroups = splitEvery(ceil(tagsCount / 4), tagsList.filteredTags); return ( @@ -54,8 +55,17 @@ export class TagsList extends React.Component { } render() { + const { filterTags } = this.props; + return (
+ {!this.props.tagsList.loading && ( + + )}
{this.renderContent()}
@@ -64,4 +74,4 @@ export class TagsList extends React.Component { } } -export default connect(pick(['tagsList']), { listTags })(TagsList); +export default connect(pick(['tagsList']), { listTags, filterTags })(TagsList); diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js index e48aa7ee..d2d7c819 100644 --- a/src/tags/reducers/tagsList.js +++ b/src/tags/reducers/tagsList.js @@ -6,9 +6,11 @@ import { TAG_EDITED } from './tagEdit'; const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START'; const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR'; const LIST_TAGS = 'shlink/tagsList/LIST_TAGS'; +const FILTER_TAGS = 'shlink/tagsList/FILTER_TAGS'; const defaultState = { tags: [], + filteredTags: [], loading: false, error: false, }; @@ -30,20 +32,31 @@ export default function reducer(state = defaultState, action) { case LIST_TAGS: return { tags: action.tags, + filteredTags: action.tags, loading: false, error: false, }; case TAG_DELETED: return { ...state, + // FIXME This should be optimized somehow... tags: reject(tag => tag === action.tag, state.tags), + filteredTags: reject(tag => tag === action.tag, state.filteredTags), }; case TAG_EDITED: + const renameTag = tag => tag === action.oldName ? action.newName : tag; return { ...state, - tags: state.tags.map( - tag => tag === action.oldName ? action.newName : tag - ).sort(), + // FIXME This should be optimized somehow... + tags: state.tags.map(renameTag).sort(), + filteredTags: state.filteredTags.map(renameTag).sort(), + }; + case FILTER_TAGS: + return { + ...state, + filteredTags: state.tags.filter( + tag => tag.toLowerCase().match(action.searchTerm), + ), }; default: return state; @@ -61,3 +74,8 @@ export const _listTags = ShlinkApiClient => async dispatch => { } }; export const listTags = () => _listTags(ShlinkApiClient); + +export const filterTags = searchTerm => ({ + type: FILTER_TAGS, + searchTerm, +}); diff --git a/src/utils/SearchField.js b/src/utils/SearchField.js index 77a569b9..b2474647 100644 --- a/src/utils/SearchField.js +++ b/src/utils/SearchField.js @@ -2,10 +2,17 @@ import React from 'react'; import FontAwesomeIcon from '@fortawesome/react-fontawesome'; import searchIcon from '@fortawesome/fontawesome-free-solid/faSearch'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import './SearchField.scss'; const propTypes = { onChange: PropTypes.func.isRequired, + className: PropTypes.string, + placeholder: PropTypes.string, +}; +const defaultProps = { + className: '', + placeholder: 'Search...', }; export default class SearchField extends React.Component { @@ -31,12 +38,14 @@ export default class SearchField extends React.Component { } render() { + const { className, placeholder } = this.props; + return ( -
+
this.searchTermChanged(e.target.value)} value={this.state.searchTerm} /> @@ -55,3 +64,4 @@ export default class SearchField extends React.Component { } SearchField.propTypes = propTypes; +SearchField.defaultProps = defaultProps;