Replaced tags component by one which is better maintained

This commit is contained in:
Alejandro Celaya 2021-06-26 10:17:07 +02:00
parent 741bc21a55
commit 66c5c7ebf1
9 changed files with 195 additions and 194 deletions

65
package-lock.json generated
View file

@ -6578,15 +6578,6 @@
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-autosuggest": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@types/react-autosuggest/-/react-autosuggest-10.1.2.tgz",
"integrity": "sha512-K23lmXhC3Bbd8y/jm5+wYrw/NAeN4U/wlHTgAEBIwLOyQKFCFYA3ONKte9P21L+RGIXRP8UlzHOSRtmIZw5Nqw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-color": { "@types/react-color": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.4.tgz",
@ -6669,10 +6660,10 @@
"@types/react-router": "*" "@types/react-router": "*"
} }
}, },
"@types/react-tagsinput": { "@types/react-tag-autocomplete": {
"version": "3.19.7", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/react-tagsinput/-/react-tagsinput-3.19.7.tgz", "resolved": "https://registry.npmjs.org/@types/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz",
"integrity": "sha512-yj/3iFBLoan/0vzXMxC9zGhO1uJ89qjQldekf0o3fX4mYdaAPW/VbP921fsyYt6PdHmJ9UMo+kERSMzUAml1xQ==", "integrity": "sha512-6qJQS81ZMaqV/ZSADwiU91TXnR6ZJINPqoV3z2SMMSlUcO6CV8Vc5QnqcqcVTj2CHnU3UQ2Q5QfSj3NyXomcDg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
@ -12919,7 +12910,8 @@
"es6-promise": { "es6-promise": {
"version": "4.2.8", "version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"dev": true
}, },
"escalade": { "escalade": {
"version": "3.1.1", "version": "3.1.1",
@ -24575,18 +24567,6 @@
} }
} }
}, },
"react-autosuggest": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-autosuggest/-/react-autosuggest-10.1.0.tgz",
"integrity": "sha512-/azBHmc6z/31s/lBf6irxPf/7eejQdR0IqnZUzjdSibtlS8+Rw/R79pgDAo6Ft5QqCUTyEQ+f0FhL+1olDQ8OA==",
"requires": {
"es6-promise": "^4.2.8",
"prop-types": "^15.7.2",
"react-themeable": "^1.1.0",
"section-iterator": "^2.0.0",
"shallow-equal": "^1.2.1"
}
},
"react-chartjs-2": { "react-chartjs-2": {
"version": "2.11.1", "version": "2.11.1",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.11.1.tgz", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.11.1.tgz",
@ -24998,25 +24978,10 @@
"resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-6.0.1.tgz", "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-6.0.1.tgz",
"integrity": "sha512-69nonicgjT4ofeHxZSpjuz37BoIiWMEbUYkX0mdTCY2mX1U53XDzDUIOVKRg6vVBNGL+pxYjbRzmylXWORh1xQ==" "integrity": "sha512-69nonicgjT4ofeHxZSpjuz37BoIiWMEbUYkX0mdTCY2mX1U53XDzDUIOVKRg6vVBNGL+pxYjbRzmylXWORh1xQ=="
}, },
"react-tagsinput": { "react-tag-autocomplete": {
"version": "3.19.0", "version": "6.1.0",
"resolved": "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.19.0.tgz", "resolved": "https://registry.npmjs.org/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz",
"integrity": "sha1-bjtFWV8tKV1GV78ZRJGYj5SMqr8=" "integrity": "sha512-AMhVqxEEIrOfzH0A9XrpsTaLZCVYgjjxp3DSTuSvx91LBSFI6uYcKe38ltR/H/TQw4aytofVghQ1hR9sKpXRQA=="
},
"react-themeable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
"integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=",
"requires": {
"object-assign": "^3.0.0"
},
"dependencies": {
"object-assign": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
"integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
}
}
}, },
"react-transition-group": { "react-transition-group": {
"version": "2.9.0", "version": "2.9.0",
@ -26121,11 +26086,6 @@
} }
} }
}, },
"section-iterator": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz",
"integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio="
},
"select-hose": { "select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@ -26374,11 +26334,6 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"shallow-equal": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
},
"shebang-command": { "shebang-command": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",

View file

@ -40,7 +40,6 @@
"qs": "^6.9.6", "qs": "^6.9.6",
"ramda": "^0.27.1", "ramda": "^0.27.1",
"react": "^17.0.1", "react": "^17.0.1",
"react-autosuggest": "^10.1.0",
"react-chartjs-2": "^2.11.1", "react-chartjs-2": "^2.11.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.2", "react-copy-to-clipboard": "^5.0.2",
@ -51,7 +50,7 @@
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-swipeable": "^6.0.1", "react-swipeable": "^6.0.1",
"react-tagsinput": "^3.19.0", "react-tag-autocomplete": "^6.1.0",
"reactstrap": "^8.9.0", "reactstrap": "^8.9.0",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-localstorage-simple": "^2.4.0", "redux-localstorage-simple": "^2.4.0",
@ -80,7 +79,6 @@
"@types/qs": "^6.9.5", "@types/qs": "^6.9.5",
"@types/ramda": "^0.27.38", "@types/ramda": "^0.27.38",
"@types/react": "^17.0.2", "@types/react": "^17.0.2",
"@types/react-autosuggest": "^10.1.2",
"@types/react-color": "^3.0.4", "@types/react-color": "^3.0.4",
"@types/react-copy-to-clipboard": "^5.0.0", "@types/react-copy-to-clipboard": "^5.0.0",
"@types/react-datepicker": "^3.1.5", "@types/react-datepicker": "^3.1.5",
@ -88,7 +86,7 @@
"@types/react-leaflet": "^2.5.2", "@types/react-leaflet": "^2.5.2",
"@types/react-redux": "^7.1.16", "@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.1.7", "@types/react-router-dom": "^5.1.7",
"@types/react-tagsinput": "^3.19.7", "@types/react-tag-autocomplete": "^6.1.0",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@wojtekmaj/enzyme-adapter-react-17": "^0.3.1", "@wojtekmaj/enzyme-adapter-react-17": "^0.3.1",
"adm-zip": "^0.4.16", "adm-zip": "^0.4.16",

View file

@ -0,0 +1,145 @@
@import '../utils/base';
.react-tags {
position: relative;
padding: 5px 0 0 6px;
border-radius: .3rem;
background-color: var(--input-color);
border: 1px solid var(--input-border-color);
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
/* shared font styles */
font-size: 1em;
line-height: 1.2;
/* clicking anywhere will focus the input */
cursor: text;
}
.react-tags.is-focused {
box-shadow: 0 0 0 .2rem rgb(70 150 229 / 25%);
}
.react-tags__tag {
font-size: 100%;
}
.react-tags__selected {
display: inline;
vertical-align: 2px;
}
.react-tags__selected-tag {
display: inline-block;
box-sizing: border-box;
margin: 0 6px 6px 0;
padding: 6px 8px;
border: 1px solid var(--input-border-color);
border-radius: .25rem;
background: #f1f1f1;
/* match the font styles */
font-size: inherit;
line-height: inherit;
}
.react-tags__selected-tag:after {
content: '\2715';
color: #aaaaaa;
margin-left: 8px;
}
.react-tags__selected-tag:hover,
.react-tags__selected-tag:focus {
border-color: var(--input-border-color);
}
.react-tags__search {
display: inline-block;
/* match tag layout */
padding: 6px 2px;
margin-bottom: 5px;
/* prevent autoresize overflowing the container */
max-width: 100%;
}
@media screen and (min-width: $smMin) {
.react-tags__search {
/* this will become the offsetParent for suggestions */
position: relative;
}
}
.react-tags__search-input {
font-size: 1.25rem;
line-height: inherit;
color: var(--input-text-color);
background-color: var(--input-color);
/* prevent autoresize overflowing the container */
max-width: 100%;
/* remove styles and layout from this element */
margin: 0 0 0 7px;
padding: 0;
border: 0;
outline: none;
}
.react-tags__search-input::-ms-clear {
display: none;
}
.react-tags__suggestions {
position: absolute;
top: 100%;
left: 0;
width: 100%;
z-index: 10;
}
@media screen and (min-width: $smMin) {
.react-tags__suggestions {
width: 240px;
}
}
.react-tags__suggestions ul {
margin: 4px -1px;
padding: 0;
list-style: none;
background: var(--primary-color);
border: 1px solid var(--border-color);
border-radius: .25rem;
box-shadow: 0 2px 6px rgba(0, 0, 0, .2);
}
.react-tags__suggestions li {
padding: 8px 10px;
}
.react-tags__suggestions li:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
.react-tags__suggestions li mark {
text-decoration: underline;
background: none;
font-weight: 600;
}
.react-tags__suggestions li:hover {
cursor: pointer;
background-color: var(--active-color);
}
.react-tags__suggestions li.is-active {
background-color: var(--active-color);
}
.react-tags__suggestions li.is-disabled {
opacity: .5;
cursor: auto;
}

View file

@ -1,58 +0,0 @@
@import '../utils/base';
.react-tagsinput {
background-color: var(--input-color);
border: 1px solid var(--input-border-color);
border-radius: .25rem;
overflow: hidden;
min-height: 2.6rem;
padding: .5rem 0 0 1rem;
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
}
.react-tagsinput--focused {
border-color: #80bdff;
box-shadow: 0 0 0 .2rem rgb(70 150 229 / 25%);
}
.react-tagsinput-tag {
font-size: 1rem;
background-color: #f1f1f1;
border-radius: 4px;
display: inline-block;
font-weight: 400;
margin: 0 5px 6px 0;
padding: 6px 8px;
line-height: 1;
color: #ffffff;
}
.react-tagsinput-remove {
cursor: pointer;
font-weight: 700;
margin-left: 8px;
}
.react-tagsinput-tag span:before {
content: '\2715';
color: #ffffff;
}
.react-tagsinput-input {
background: transparent;
border: 0;
outline: none;
padding: 1px 0;
width: 100%;
margin-bottom: 6px;
font-size: 1.25rem;
color: var(--input-text-color);
}
.react-tagsinput-input::placeholder {
color: $textPlaceholder;
}
.react-autosuggest__suggestion--highlighted {
background-color: var(--active-color);
}

View file

@ -2,7 +2,7 @@
@import './utils/base'; @import './utils/base';
@import 'node_modules/bootstrap/scss/bootstrap.scss'; @import 'node_modules/bootstrap/scss/bootstrap.scss';
@import './common/react-tagsinput.scss'; @import './common/react-tag-autocomplete.scss';
@import './theme/theme'; @import './theme/theme';
* { * {

View file

@ -97,7 +97,7 @@ export const ShortUrlForm = (
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<TagsSelector tags={shortUrlData.tags ?? []} onChange={changeTags} /> <TagsSelector selectedTags={shortUrlData.tags ?? []} onChange={changeTags} />
</FormGroup> </FormGroup>
</> </>
); );

View file

@ -1,18 +1,19 @@
import { FC } from 'react'; import { FC, MouseEventHandler } from 'react';
import ColorGenerator from '../../utils/services/ColorGenerator'; import ColorGenerator from '../../utils/services/ColorGenerator';
import './Tag.scss'; import './Tag.scss';
interface TagProps { interface TagProps {
colorGenerator: ColorGenerator; colorGenerator: ColorGenerator;
text: string; text: string;
className?: string;
clearable?: boolean; clearable?: boolean;
onClick?: () => void; onClick?: MouseEventHandler;
onClose?: () => void; onClose?: MouseEventHandler;
} }
const Tag: FC<TagProps> = ({ text, children, clearable, colorGenerator, onClick, onClose }) => ( const Tag: FC<TagProps> = ({ text, children, clearable, className = '', colorGenerator, onClick, onClose }) => (
<span <span
className="badge tag" className={`badge tag ${className}`}
style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable || !onClick ? 'auto' : 'pointer' }} style={{ backgroundColor: colorGenerator.getColorForKey(text), cursor: clearable || !onClick ? 'auto' : 'pointer' }}
onClick={onClick} onClick={onClick}
> >

View file

@ -1,16 +0,0 @@
@import '../../utils/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;
}

View file

@ -1,13 +1,12 @@
import { ChangeEvent, useEffect } from 'react'; import { useEffect } from 'react';
import TagsInput, { RenderInputProps, RenderTagProps } from 'react-tagsinput'; import ReactTags, { SuggestionComponentProps, TagComponentProps } from 'react-tag-autocomplete';
import Autosuggest, { ChangeEvent as AutoChangeEvent, SuggestionSelectedEventData } from 'react-autosuggest';
import ColorGenerator from '../../utils/services/ColorGenerator'; import ColorGenerator from '../../utils/services/ColorGenerator';
import { TagsList } from '../reducers/tagsList'; import { TagsList } from '../reducers/tagsList';
import TagBullet from './TagBullet'; import TagBullet from './TagBullet';
import './TagsSelector.scss'; import Tag from './Tag';
export interface TagsSelectorProps { export interface TagsSelectorProps {
tags: string[]; selectedTags: string[];
onChange: (tags: string[]) => void; onChange: (tags: string[]) => void;
placeholder?: string; placeholder?: string;
} }
@ -17,65 +16,42 @@ interface TagsSelectorConnectProps extends TagsSelectorProps {
tagsList: TagsList; tagsList: TagsList;
} }
const noop = () => {}; const toComponentTag = (tag: string) => ({ id: tag, name: tag });
const TagsSelector = (colorGenerator: ColorGenerator) => ( const TagsSelector = (colorGenerator: ColorGenerator) => (
{ tags, onChange, listTags, tagsList, placeholder = 'Add tags to the URL' }: TagsSelectorConnectProps, { selectedTags, onChange, listTags, tagsList, placeholder = 'Add tags to the URL' }: TagsSelectorConnectProps,
) => { ) => {
useEffect(() => { useEffect(() => {
listTags(); listTags();
}, []); }, []);
const renderTag = ( const renderTag = ({ tag, onDelete }: TagComponentProps) =>
{ tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }: RenderTagProps<string>, <Tag colorGenerator={colorGenerator} text={tag.name} clearable className="react-tags__tag" onClose={onDelete} />;
) => ( const renderSuggestion = ({ item }: SuggestionComponentProps) => (
<span key={key} style={{ backgroundColor: colorGenerator.getColorForKey(tag) }} {...other}> <>
{getTagDisplayValue(tag)} <TagBullet tag={`${item.name}`} colorGenerator={colorGenerator} />
{!disabled && <span className={classNameRemove} onClick={() => onRemove(key)} />} {item.name}
</span> </>
); );
const renderAutocompleteInput = (data: RenderInputProps<string>) => {
const { addTag, ...otherProps } = data;
const handleOnChange = (e: ChangeEvent<HTMLInputElement>, { method }: AutoChangeEvent) => {
method === 'enter' ? e.preventDefault() : otherProps.onChange(e);
};
const inputValue = otherProps.value?.trim().toLowerCase() ?? '';
const suggestions = tagsList.tags.filter((tag) => tag.startsWith(inputValue));
return (
<Autosuggest
ref={otherProps.ref}
suggestions={suggestions}
inputProps={{ ...otherProps, onChange: handleOnChange }}
highlightFirstSuggestion
shouldRenderSuggestions={(value: string) => value.trim().length > 0}
getSuggestionValue={(suggestion) => suggestion}
renderSuggestion={(suggestion) => (
<>
<TagBullet tag={suggestion} colorGenerator={colorGenerator} />
{suggestion}
</>
)}
onSuggestionsFetchRequested={noop}
onSuggestionsClearRequested={noop}
onSuggestionSelected={(_, { suggestion }: SuggestionSelectedEventData<string>) => {
addTag(suggestion);
}}
/>
);
};
return ( return (
<TagsInput <ReactTags
value={tags} tags={selectedTags.map(toComponentTag)}
inputProps={{ placeholder }} tagComponent={renderTag}
onlyUnique suggestions={tagsList.tags.filter((tag) => !selectedTags.includes(tag)).map(toComponentTag)}
renderTag={renderTag} suggestionComponent={renderSuggestion}
renderInput={renderAutocompleteInput} allowNew
// FIXME Workaround to be able to add tags on Android placeholderText={placeholder}
addOnBlur onDelete={(removedTagIndex) => {
onChange={onChange} selectedTags.splice(removedTagIndex, 1);
onChange(selectedTags);
}}
onAddition={({ name: newTag }) => {
const tags = [ ...selectedTags, newTag.toLowerCase() ]; // eslint-disable-line @typescript-eslint/no-unsafe-call
onChange(tags);
}}
/> />
); );
}; };