Skip to content

Commit ea791e6

Browse files
committed
Migrate from react-tag-autocomplete to TagsAutocomplete component
1 parent 146f2c3 commit ea791e6

6 files changed

Lines changed: 12 additions & 246 deletions

File tree

package-lock.json

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"react-external-link": "^2.5.0",
7474
"react-leaflet": "^4.2.1 || ^5.0",
7575
"react-swipeable": "^7.0.2",
76-
"react-tag-autocomplete": "^7.5.0",
7776
"recharts": "^2.15.3"
7877
},
7978
"devDependencies": {

src/index.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
@use '../node_modules/@shlinkio/shlink-frontend-kit/dist/base';
2-
@use './tags/react-tag-autocomplete';
32
@use 'leaflet/dist/leaflet.css';
43

54
dialog .leaflet-top .leaflet-control {

src/short-urls/ShortUrlsFilteringBar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ const ShortUrlsFilteringBar: FCWithDeps<ShortUrlsFilteringConnectProps, ShortUrl
9090
tags={tagsList.tags}
9191
selectedTags={tags}
9292
onChange={changeTagSelection}
93+
containerClassName={clsx(tags.length > 1 && 'tw:[&]:rounded-r-none')}
9394
/>
9495
</div>
9596
{tags.length > 1 && (

src/tags/helpers/TagsSelector.tsx

Lines changed: 11 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,31 @@
1-
import { clsx } from 'clsx';
2-
import type { RefObject } from 'react';
3-
import { useRef } from 'react';
4-
import type { OptionRendererProps, ReactTagsAPI, TagRendererProps, TagSuggestion } from 'react-tag-autocomplete';
5-
import { ReactTags } from 'react-tag-autocomplete';
1+
import type { TagsAutocompleteProps } from '@shlinkio/shlink-frontend-kit/tailwind';
2+
import { TagsAutocomplete } from '@shlinkio/shlink-frontend-kit/tailwind';
63
import type { FCWithDeps } from '../../container/utils';
74
import { componentFactory, useDependencies } from '../../container/utils';
85
import { useSetting } from '../../settings';
96
import type { ColorGenerator } from '../../utils/services/ColorGenerator';
10-
import { normalizeTag } from './index';
11-
import { Tag } from './Tag';
12-
import { TagBullet } from './TagBullet';
13-
14-
let tagId = 1;
15-
16-
const NOT_FOUND_TAG = 'Tag not found';
17-
const NEW_TAG = 'Add tag';
18-
const isSelectableOption = (tag: string) => tag !== NOT_FOUND_TAG;
19-
const isNewOption = (tag: string) => tag === NEW_TAG;
20-
const toTagObject = (tag: string): TagSuggestion => {
21-
// react-tag-autocomplete uses the value to determine if a tag is already added, removing it in that case.
22-
// Using a unique value ensures all tags are always considered new, together with a `Set` to ignore duplicates later.
23-
tagId += 1;
24-
return { label: tag, value: `${tag}${tagId}` };
25-
};
26-
27-
const buildTagRenderer = (colorGenerator: ColorGenerator) => ({ tag, onClick: deleteTag }: TagRendererProps) => (
28-
<Tag colorGenerator={colorGenerator} text={tag.label} className="react-tags__tag" onClose={deleteTag} />
29-
);
30-
const buildOptionRenderer = (colorGenerator: ColorGenerator, api: RefObject<ReactTagsAPI | null>) => (
31-
{ option, classNames: classes, ...rest }: OptionRendererProps,
32-
) => {
33-
const isSelectable = isSelectableOption(option.label);
34-
const isNew = isNewOption(option.label);
35-
36-
return (
37-
<div
38-
className={clsx(classes.option, {
39-
[classes.optionIsActive]: isSelectable && option.active,
40-
'react-tags__listbox-option--not-selectable': !isSelectable,
41-
})}
42-
{...rest}
43-
>
44-
{!isSelectable ? <i>{option.label}</i> : (
45-
<>
46-
{!isNew && <TagBullet tag={`${option.label}`} colorGenerator={colorGenerator} />}
47-
{!isNew ? option.label : <i>Add &quot;{normalizeTag(api.current?.input.value ?? '')}&quot;</i>}
48-
</>
49-
)}
50-
</div>
51-
);
52-
};
537

548
export type TagsSelectorProps = {
55-
tags: string[];
56-
selectedTags: string[];
579
onChange: (tags: string[]) => void;
58-
placeholder?: string;
59-
/** If true, it won't allow adding new tags */
60-
immutable?: boolean;
61-
};
10+
} & Pick<TagsAutocompleteProps, 'tags' | 'selectedTags' | 'placeholder' | 'immutable' | 'containerClassName'>;
6211

6312
type TagsSelectorDeps = {
6413
ColorGenerator: ColorGenerator;
6514
};
6615

67-
const TagsSelector: FCWithDeps<TagsSelectorProps, TagsSelectorDeps> = (
68-
{ selectedTags, onChange, placeholder, tags, immutable = false },
69-
) => {
16+
const TagsSelector: FCWithDeps<TagsSelectorProps, TagsSelectorDeps> = ({ onChange, placeholder, ...rest }) => {
7017
const { ColorGenerator: colorGenerator } = useDependencies(TagsSelector);
7118
const shortUrlCreation = useSetting('shortUrlCreation');
7219
const searchMode = shortUrlCreation?.tagFilteringMode ?? 'startsWith';
73-
const apiRef = useRef<ReactTagsAPI>(null);
7420

7521
return (
76-
<ReactTags
77-
ref={apiRef}
78-
selected={selectedTags.map(toTagObject)}
79-
suggestions={tags.filter((tag) => !selectedTags.includes(tag)).map(toTagObject)}
80-
renderTag={buildTagRenderer(colorGenerator)}
81-
// eslint-disable-next-line react-compiler/react-compiler
82-
renderOption={buildOptionRenderer(colorGenerator, apiRef)}
83-
activateFirstOption
84-
allowNew={!immutable}
85-
newOptionText={NEW_TAG}
86-
noOptionsText={NOT_FOUND_TAG}
87-
placeholderText={placeholder ?? 'Add tags to the URL'}
88-
delimiterKeys={['Enter', ',']}
89-
suggestionsTransform={
90-
(query, suggestions) => {
91-
const searchTerm = query.toLowerCase().trim();
92-
return searchTerm.length < 1 ? [] : [...suggestions.filter(
93-
({ label }) => (searchMode === 'includes' ? label.includes(searchTerm) : label.startsWith(searchTerm)),
94-
)].slice(0, 5);
95-
}
96-
}
97-
onDelete={(removedTagIndex) => {
98-
const tagsCopy = [...selectedTags];
99-
tagsCopy.splice(removedTagIndex, 1);
100-
onChange(tagsCopy);
101-
}}
102-
onAdd={({ label: newTag }) => onChange(
103-
// Use a Set to ignore duplicated tags.
104-
// Split any of the new tags by comma, allowing to paste multiple comma-separated tags at once.
105-
[...new Set([...selectedTags, ...newTag.split(',').map(normalizeTag)])],
106-
)}
22+
<TagsAutocomplete
23+
{...rest}
24+
onTagsChange={onChange}
25+
getColorForTag={(tag) => colorGenerator.getColorForKey(tag)}
26+
size="lg"
27+
placeholder={placeholder ?? 'Add tags to the URL'}
28+
searchMode={searchMode}
10729
/>
10830
);
10931
};

src/tags/react-tag-autocomplete.scss

Lines changed: 0 additions & 142 deletions
This file was deleted.

0 commit comments

Comments
 (0)