mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-04-20 05:26:20 +00:00
Added first autocomplete implementation on tags selector
This commit is contained in:
@@ -5,7 +5,7 @@ import editIcon from '@fortawesome/fontawesome-free-solid/faPencilAlt';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import colorGenerator, { colorGeneratorType } from '../utils/ColorGenerator';
|
||||
import TagBullet from '../utils/TagBullet';
|
||||
import './TagCard.scss';
|
||||
import DeleteTagConfirmModal from './helpers/DeleteTagConfirmModal';
|
||||
import EditTagModal from './helpers/EditTagModal';
|
||||
@@ -14,16 +14,12 @@ export default class TagCard extends React.Component {
|
||||
static propTypes = {
|
||||
tag: PropTypes.string,
|
||||
currentServerId: PropTypes.string,
|
||||
colorGenerator: colorGeneratorType,
|
||||
};
|
||||
static defaultProps = {
|
||||
colorGenerator,
|
||||
};
|
||||
|
||||
state = { isDeleteModalOpen: false, isEditModalOpen: false };
|
||||
|
||||
render() {
|
||||
const { tag, colorGenerator, currentServerId } = this.props;
|
||||
const { tag, currentServerId } = this.props;
|
||||
const toggleDelete = () =>
|
||||
this.setState(({ isDeleteModalOpen }) => ({ isDeleteModalOpen: !isDeleteModalOpen }));
|
||||
const toggleEdit = () =>
|
||||
@@ -45,10 +41,7 @@ export default class TagCard extends React.Component {
|
||||
<FontAwesomeIcon icon={editIcon} />
|
||||
</button>
|
||||
<h5 className="tag-card__tag-title">
|
||||
<div
|
||||
style={{ backgroundColor: colorGenerator.getColorForKey(tag) }}
|
||||
className="tag-card__tag-bullet"
|
||||
/>
|
||||
<TagBullet tag={tag} />
|
||||
<Link to={`/server/${currentServerId}/list-short-urls/1?tag=${tag}`}>
|
||||
{tag}
|
||||
</Link>
|
||||
|
||||
@@ -16,17 +16,6 @@
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.tag-card__tag-bullet {
|
||||
$width: 20px;
|
||||
|
||||
border-radius: 50%;
|
||||
width: $width;
|
||||
height: $width;
|
||||
display: inline-block;
|
||||
vertical-align: -4px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.tag-card__btn {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@@ -59,9 +59,7 @@ export default function reducer(state = defaultState, action) {
|
||||
case FILTER_TAGS:
|
||||
return {
|
||||
...state,
|
||||
filteredTags: state.tags.filter(
|
||||
(tag) => tag.toLowerCase().match(action.searchTerm),
|
||||
),
|
||||
filteredTags: state.tags.filter((tag) => tag.toLowerCase().match(action.searchTerm)),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
24
src/utils/TagBullet.js
Normal file
24
src/utils/TagBullet.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import colorGenerator, { colorGeneratorType } from './ColorGenerator';
|
||||
import './TagBullet.scss';
|
||||
|
||||
const propTypes = {
|
||||
tag: PropTypes.string.isRequired,
|
||||
colorGenerator: colorGeneratorType,
|
||||
};
|
||||
const defaultProps = {
|
||||
colorGenerator,
|
||||
};
|
||||
|
||||
export default function TagBullet({ tag, colorGenerator }) {
|
||||
return (
|
||||
<div
|
||||
style={{ backgroundColor: colorGenerator.getColorForKey(tag) }}
|
||||
className="tag-bullet"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TagBullet.propTypes = propTypes;
|
||||
TagBullet.defaultProps = defaultProps;
|
||||
10
src/utils/TagBullet.scss
Normal file
10
src/utils/TagBullet.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.tag-bullet {
|
||||
$width: 20px;
|
||||
|
||||
border-radius: 50%;
|
||||
width: $width;
|
||||
height: $width;
|
||||
display: inline-block;
|
||||
vertical-align: -4px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
@@ -1,40 +1,102 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import TagsInput from 'react-tagsinput';
|
||||
import PropTypes from 'prop-types';
|
||||
import Autosuggest from 'react-autosuggest';
|
||||
import { pick, identity } from 'ramda';
|
||||
import { listTags } from '../tags/reducers/tagsList';
|
||||
import colorGenerator, { colorGeneratorType } from './ColorGenerator';
|
||||
import './TagsSelector.scss';
|
||||
import TagBullet from './TagBullet';
|
||||
|
||||
const defaultProps = {
|
||||
colorGenerator,
|
||||
placeholder: 'Add tags to the URL',
|
||||
};
|
||||
const propTypes = {
|
||||
tags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
colorGenerator: colorGeneratorType,
|
||||
};
|
||||
export class TagsSelectorComponent extends React.Component {
|
||||
static propTypes = {
|
||||
tags: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
colorGenerator: colorGeneratorType,
|
||||
tagsList: PropTypes.shape({
|
||||
tags: PropTypes.arrayOf(PropTypes.string),
|
||||
}),
|
||||
};
|
||||
static defaultProps = {
|
||||
colorGenerator,
|
||||
placeholder: 'Add tags to the URL',
|
||||
};
|
||||
|
||||
export default function TagsSelector({ tags, onChange, placeholder, colorGenerator }) {
|
||||
const renderTag = ({ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }) => (
|
||||
<span key={key} style={{ backgroundColor: colorGenerator.getColorForKey(tag) }} {...other}>
|
||||
{getTagDisplayValue(tag)}
|
||||
{!disabled && <span className={classNameRemove} onClick={() => onRemove(key)} />}
|
||||
</span>
|
||||
);
|
||||
componentDidMount() {
|
||||
const { listTags } = this.props;
|
||||
|
||||
return (
|
||||
<TagsInput
|
||||
value={tags}
|
||||
inputProps={{ placeholder }}
|
||||
onlyUnique
|
||||
renderTag={renderTag}
|
||||
listTags();
|
||||
}
|
||||
|
||||
// FIXME Workaround to be able to add tags on Android
|
||||
addOnBlur
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
render() {
|
||||
const { tags, onChange, placeholder, colorGenerator, tagsList } = this.props;
|
||||
const renderTag = ({ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }) => (
|
||||
<span key={key} style={{ backgroundColor: colorGenerator.getColorForKey(tag) }} {...other}>
|
||||
{getTagDisplayValue(tag)}
|
||||
{!disabled && <span className={classNameRemove} onClick={() => onRemove(key)} />}
|
||||
</span>
|
||||
);
|
||||
const renderAutocompleteInput = (data) => {
|
||||
const { addTag, ...rest } = data;
|
||||
|
||||
const handleOnChange = (e, { method }) => {
|
||||
if (method === 'enter') {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
rest.onChange(e);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-extra-parens
|
||||
const inputValue = (rest.value && rest.value.trim().toLowerCase()) || '';
|
||||
const inputLength = inputValue.length;
|
||||
const suggestions = tagsList.tags.filter((state) => state.toLowerCase().slice(0, inputLength) === inputValue);
|
||||
|
||||
return (
|
||||
<Autosuggest
|
||||
ref={rest.ref}
|
||||
suggestions={suggestions}
|
||||
inputProps={{ ...rest, onChange: handleOnChange }}
|
||||
highlightFirstSuggestion
|
||||
shouldRenderSuggestions={(value) => value && value.trim().length > 0}
|
||||
getSuggestionValue={(suggestion) => {
|
||||
console.log(suggestion);
|
||||
|
||||
return suggestion;
|
||||
}}
|
||||
renderSuggestion={(suggestion) => (
|
||||
<React.Fragment>
|
||||
<TagBullet tag={suggestion} />
|
||||
{suggestion}
|
||||
</React.Fragment>
|
||||
)}
|
||||
onSuggestionSelected={(e, { suggestion }) => {
|
||||
addTag(suggestion);
|
||||
}}
|
||||
onSuggestionsClearRequested={identity}
|
||||
onSuggestionsFetchRequested={identity}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TagsInput
|
||||
value={tags}
|
||||
inputProps={{ placeholder }}
|
||||
onlyUnique
|
||||
renderTag={renderTag}
|
||||
renderInput={renderAutocompleteInput}
|
||||
|
||||
// FIXME Workaround to be able to add tags on Android
|
||||
addOnBlur
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TagsSelector.defaultProps = defaultProps;
|
||||
TagsSelector.propTypes = propTypes;
|
||||
const TagsSelector = connect(pick([ 'tagsList' ]), { listTags })(TagsSelectorComponent);
|
||||
|
||||
export default TagsSelector;
|
||||
|
||||
16
src/utils/TagsSelector.scss
Normal file
16
src/utils/TagsSelector.scss
Normal file
@@ -0,0 +1,16 @@
|
||||
@import './base';
|
||||
|
||||
.react-autosuggest__suggestions-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.react-autosuggest__suggestion {
|
||||
margin-left: -6px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.react-autosuggest__suggestion--highlighted {
|
||||
background-color: $lightGrey;
|
||||
}
|
||||
Reference in New Issue
Block a user