From 66c5c7ebf14656dd6a868dd61120d8fdcabe0d02 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 26 Jun 2021 10:17:07 +0200 Subject: [PATCH] Replaced tags component by one which is better maintained --- package-lock.json | 65 ++--------- package.json | 6 +- src/common/react-tag-autocomplete.scss | 145 +++++++++++++++++++++++++ src/common/react-tagsinput.scss | 58 ---------- src/index.scss | 2 +- src/short-urls/ShortUrlForm.tsx | 2 +- src/tags/helpers/Tag.tsx | 11 +- src/tags/helpers/TagsSelector.scss | 16 --- src/tags/helpers/TagsSelector.tsx | 84 +++++--------- 9 files changed, 195 insertions(+), 194 deletions(-) create mode 100644 src/common/react-tag-autocomplete.scss delete mode 100644 src/common/react-tagsinput.scss delete mode 100644 src/tags/helpers/TagsSelector.scss diff --git a/package-lock.json b/package-lock.json index 6063c83f..2d73c4b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6578,15 +6578,6 @@ "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": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.4.tgz", @@ -6669,10 +6660,10 @@ "@types/react-router": "*" } }, - "@types/react-tagsinput": { - "version": "3.19.7", - "resolved": "https://registry.npmjs.org/@types/react-tagsinput/-/react-tagsinput-3.19.7.tgz", - "integrity": "sha512-yj/3iFBLoan/0vzXMxC9zGhO1uJ89qjQldekf0o3fX4mYdaAPW/VbP921fsyYt6PdHmJ9UMo+kERSMzUAml1xQ==", + "@types/react-tag-autocomplete": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz", + "integrity": "sha512-6qJQS81ZMaqV/ZSADwiU91TXnR6ZJINPqoV3z2SMMSlUcO6CV8Vc5QnqcqcVTj2CHnU3UQ2Q5QfSj3NyXomcDg==", "dev": true, "requires": { "@types/react": "*" @@ -12919,7 +12910,8 @@ "es6-promise": { "version": "4.2.8", "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": { "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": { "version": "2.11.1", "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", "integrity": "sha512-69nonicgjT4ofeHxZSpjuz37BoIiWMEbUYkX0mdTCY2mX1U53XDzDUIOVKRg6vVBNGL+pxYjbRzmylXWORh1xQ==" }, - "react-tagsinput": { - "version": "3.19.0", - "resolved": "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.19.0.tgz", - "integrity": "sha1-bjtFWV8tKV1GV78ZRJGYj5SMqr8=" - }, - "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-tag-autocomplete": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-tag-autocomplete/-/react-tag-autocomplete-6.1.0.tgz", + "integrity": "sha512-AMhVqxEEIrOfzH0A9XrpsTaLZCVYgjjxp3DSTuSvx91LBSFI6uYcKe38ltR/H/TQw4aytofVghQ1hR9sKpXRQA==" }, "react-transition-group": { "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -26374,11 +26334,6 @@ "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", diff --git a/package.json b/package.json index 911cbd65..a6c00e59 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "qs": "^6.9.6", "ramda": "^0.27.1", "react": "^17.0.1", - "react-autosuggest": "^10.1.0", "react-chartjs-2": "^2.11.1", "react-color": "^2.19.3", "react-copy-to-clipboard": "^5.0.2", @@ -51,7 +50,7 @@ "react-redux": "^7.2.2", "react-router-dom": "^5.2.0", "react-swipeable": "^6.0.1", - "react-tagsinput": "^3.19.0", + "react-tag-autocomplete": "^6.1.0", "reactstrap": "^8.9.0", "redux": "^4.0.5", "redux-localstorage-simple": "^2.4.0", @@ -80,7 +79,6 @@ "@types/qs": "^6.9.5", "@types/ramda": "^0.27.38", "@types/react": "^17.0.2", - "@types/react-autosuggest": "^10.1.2", "@types/react-color": "^3.0.4", "@types/react-copy-to-clipboard": "^5.0.0", "@types/react-datepicker": "^3.1.5", @@ -88,7 +86,7 @@ "@types/react-leaflet": "^2.5.2", "@types/react-redux": "^7.1.16", "@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", "@wojtekmaj/enzyme-adapter-react-17": "^0.3.1", "adm-zip": "^0.4.16", diff --git a/src/common/react-tag-autocomplete.scss b/src/common/react-tag-autocomplete.scss new file mode 100644 index 00000000..b41d7b7c --- /dev/null +++ b/src/common/react-tag-autocomplete.scss @@ -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; +} diff --git a/src/common/react-tagsinput.scss b/src/common/react-tagsinput.scss deleted file mode 100644 index 6ecd1cd3..00000000 --- a/src/common/react-tagsinput.scss +++ /dev/null @@ -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); -} diff --git a/src/index.scss b/src/index.scss index 5bcc10ae..5a330475 100644 --- a/src/index.scss +++ b/src/index.scss @@ -2,7 +2,7 @@ @import './utils/base'; @import 'node_modules/bootstrap/scss/bootstrap.scss'; -@import './common/react-tagsinput.scss'; +@import './common/react-tag-autocomplete.scss'; @import './theme/theme'; * { diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index 7fcfba3e..368da62e 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -97,7 +97,7 @@ export const ShortUrlForm = ( - + ); diff --git a/src/tags/helpers/Tag.tsx b/src/tags/helpers/Tag.tsx index cf6da951..895caded 100644 --- a/src/tags/helpers/Tag.tsx +++ b/src/tags/helpers/Tag.tsx @@ -1,18 +1,19 @@ -import { FC } from 'react'; +import { FC, MouseEventHandler } from 'react'; import ColorGenerator from '../../utils/services/ColorGenerator'; import './Tag.scss'; interface TagProps { colorGenerator: ColorGenerator; text: string; + className?: string; clearable?: boolean; - onClick?: () => void; - onClose?: () => void; + onClick?: MouseEventHandler; + onClose?: MouseEventHandler; } -const Tag: FC = ({ text, children, clearable, colorGenerator, onClick, onClose }) => ( +const Tag: FC = ({ text, children, clearable, className = '', colorGenerator, onClick, onClose }) => ( diff --git a/src/tags/helpers/TagsSelector.scss b/src/tags/helpers/TagsSelector.scss deleted file mode 100644 index aba09e1b..00000000 --- a/src/tags/helpers/TagsSelector.scss +++ /dev/null @@ -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; -} diff --git a/src/tags/helpers/TagsSelector.tsx b/src/tags/helpers/TagsSelector.tsx index 1611ac03..f3a64002 100644 --- a/src/tags/helpers/TagsSelector.tsx +++ b/src/tags/helpers/TagsSelector.tsx @@ -1,13 +1,12 @@ -import { ChangeEvent, useEffect } from 'react'; -import TagsInput, { RenderInputProps, RenderTagProps } from 'react-tagsinput'; -import Autosuggest, { ChangeEvent as AutoChangeEvent, SuggestionSelectedEventData } from 'react-autosuggest'; +import { useEffect } from 'react'; +import ReactTags, { SuggestionComponentProps, TagComponentProps } from 'react-tag-autocomplete'; import ColorGenerator from '../../utils/services/ColorGenerator'; import { TagsList } from '../reducers/tagsList'; import TagBullet from './TagBullet'; -import './TagsSelector.scss'; +import Tag from './Tag'; export interface TagsSelectorProps { - tags: string[]; + selectedTags: string[]; onChange: (tags: string[]) => void; placeholder?: string; } @@ -17,65 +16,42 @@ interface TagsSelectorConnectProps extends TagsSelectorProps { tagsList: TagsList; } -const noop = () => {}; +const toComponentTag = (tag: string) => ({ id: tag, name: tag }); 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(() => { listTags(); }, []); - const renderTag = ( - { tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other }: RenderTagProps, - ) => ( - - {getTagDisplayValue(tag)} - {!disabled && onRemove(key)} />} - + const renderTag = ({ tag, onDelete }: TagComponentProps) => + ; + const renderSuggestion = ({ item }: SuggestionComponentProps) => ( + <> + + {item.name} + ); - const renderAutocompleteInput = (data: RenderInputProps) => { - const { addTag, ...otherProps } = data; - const handleOnChange = (e: ChangeEvent, { 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 ( - value.trim().length > 0} - getSuggestionValue={(suggestion) => suggestion} - renderSuggestion={(suggestion) => ( - <> - - {suggestion} - - )} - onSuggestionsFetchRequested={noop} - onSuggestionsClearRequested={noop} - onSuggestionSelected={(_, { suggestion }: SuggestionSelectedEventData) => { - addTag(suggestion); - }} - /> - ); - }; return ( - !selectedTags.includes(tag)).map(toComponentTag)} + suggestionComponent={renderSuggestion} + allowNew + placeholderText={placeholder} + onDelete={(removedTagIndex) => { + selectedTags.splice(removedTagIndex, 1); + + onChange(selectedTags); + }} + onAddition={({ name: newTag }) => { + const tags = [ ...selectedTags, newTag.toLowerCase() ]; // eslint-disable-line @typescript-eslint/no-unsafe-call + + onChange(tags); + }} /> ); };