Skip to content

Feature/multiple values per tag #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -55,21 +55,23 @@ export default Example;

## 👀 Props

| Prop | Description | Type | Default |
| ------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------- | --------------- |
| `name` | value for name of input | `string` | |
| `placeholder` | placeholder for text input | `string` | |
| `value` | initial tags | `string[]` | `[]` |
| `onChange` | onChange callback (added/removed) | `string[]` | |
| `classNames` | className for styling input and tags (i.e {tag:'tag-cls', input: 'input-cls'}) | `object[tag, input]` | |
| `onKeyUp` | input `onKeyUp` callback | `event` | |
| `onBlur` | input `onBlur` callback | `event` | |
| `separators` | when to add tag (i.e. `Space`,`Enter`) | `string[]` | `["Enter"]` |
| `removers` | Remove last tag if textbox empty and `Backspace` is pressed | `string[]` | `["Backspace"]` |
| `onExisting` | if tag is already added then callback | `(tag: string) => void` | |
| `onRemoved` | on tag removed callback | `(tag: string) => void` | |
| `beforeAddValidate` | Custom validation before adding tag | `(tag: string, existingTags: string[]) => boolean` | |
| `isEditOnRemove` | Remove the tag but keep the word in the input to edit it on using Backscape Key | `boolean` | `false` |
| Prop | Description | Type | Default |
| ----------------------| ------------------------------------------------------------------------------- | -------------------------------------------------- | --------------- |
| `name` | value for name of input | `string` | |
| `placeholder` | placeholder for text input | `string` | |
| `value` | initial tags | `string[]` | `[]` |
| `onChange` | onChange callback (added/removed) | `string[]` | |
| `classNames` | className for styling input and tags (i.e {tag:'tag-cls', input: 'input-cls'}) | `object[tag, input]` | |
| `onKeyUp` | input `onKeyUp` callback | `event` | |
| `onBlur` | input `onBlur` callback | `event` | |
| `separators` | when to add tag (i.e. `Space`,`Enter`) | `string[]` | `["Enter"]` |
| `removers` | Remove last tag if textbox empty and `Backspace` is pressed | `string[]` | `["Backspace"]` |
| `onExisting` | if tag is already added then callback | `(tag: string) => void` | |
| `onRemoved` | on tag removed callback | `(tag: string) => void` | |
| `beforeAddValidate` | Custom validation before adding tag | `(tag: string, existingTags: string[]) => boolean` | |
| `isEditOnRemove` | Remove the tag but keep the word in the input to edit it on using Backscape Key | `boolean` | `false` |
| `multipleValues` | Using multiple values per tag | `boolean` | `false` |
| `numberOfValuesPerTag`| The number of values a tag can have, if 'multipleValues' is true | `number` | |

## 💅 Themeing

66 changes: 48 additions & 18 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -23,28 +23,34 @@ export interface TagsInputProps {
classNames?: {
input?: string;
tag?: string;
tagInput?: string
};
multiValueTags?: boolean;
numberOfValuesPerTag?: number;
}

const defaultSeparators = ["Enter"];

export const TagsInput = ({
name,
placeHolder,
value,
onChange,
onBlur,
separators,
disableBackspaceRemove,
onExisting,
onRemoved,
disabled,
isEditOnRemove,
beforeAddValidate,
onKeyUp,
classNames,
}: TagsInputProps) => {
name,
placeHolder,
value,
onChange,
onBlur,
separators,
disableBackspaceRemove,
onExisting,
onRemoved,
disabled,
isEditOnRemove,
beforeAddValidate,
onKeyUp,
classNames,
multiValueTags,
numberOfValuesPerTag,
}: TagsInputProps) => {
const [tags, setTags] = useState<any>(value || []);
const [openTag, setOpenTag] = useState(false);

useDidUpdateEffect(() => {
onChange && onChange(tags);
@@ -56,6 +62,7 @@ export const TagsInput = ({
}
}, [value]);


const handleOnKeyUp = e => {
e.stopPropagation();

@@ -79,23 +86,46 @@ export const TagsInput = ({
onExisting && onExisting(text);
return;
}
setTags([...tags, text]);
e.target.value = "";


if (multiValueTags) {
!openTag && setTags([...tags, [text]]);

if (openTag) {
let lastTag = JSON.parse(JSON.stringify(tags[tags.length - 1]));
setTags([...tags.slice(0, -1), [...lastTag, text]]);

numberOfValuesPerTag && ([...lastTag, text].length == numberOfValuesPerTag) && setOpenTag(false);
} else {
setOpenTag(true);

}
e.target.value = "";


} else {
setTags([...tags, text]);
e.target.value = "";
}
}
};

const onTagRemove = text => {
setTags(tags.filter(tag => tag !== text));
onRemoved && onRemoved(text);
setOpenTag(false);
};

return (
<div aria-labelledby={name} className="rti--container">
{tags.map(tag => (
{tags.map((tag, index: number) => (
<Tag
key={tag}
className={classNames?.tag}
classNameInput={classNames?.tagInput}
text={tag}
openTag={index == (tags.length - 1) && openTag}
handleKeyUp={handleOnKeyUp}
remove={onTagRemove}
disabled={disabled}
/>
15 changes: 13 additions & 2 deletions src/styles.css
Original file line number Diff line number Diff line change
@@ -36,8 +36,19 @@
font-size: inherit;
line-height: inherit;
width: 50%;
}

}
.rti--input--tag {
border: 0;
outline: 0;
font-size: inherit;
line-height: inherit;
margin-left:5px;
margin-right:5px;
height:20px;
border-radius: 5px;
background-color: rgba(256,256,256,0.6);
}
.rti--tag {
align-items: center;
background: var(--rti-tag);
@@ -58,4 +69,4 @@

.rti--tag button:hover {
color: var(--rti-tag-remove);
}
}
19 changes: 16 additions & 3 deletions src/tag.tsx
Original file line number Diff line number Diff line change
@@ -2,21 +2,34 @@ import React from "react";
import cc from "./classnames";

interface TagProps {
text: string;
text: string | string[];
remove: any;
openTag?:boolean;
disabled?: boolean;
className?: string;
classNameInput?:string;
handleKeyUp?:(e) => void;
}

export default function Tag({ text, remove, disabled, className }: TagProps) {
export default function Tag({ text, remove, disabled, className,openTag,handleKeyUp,classNameInput }: TagProps) {
const handleOnRemove = e => {
e.stopPropagation();
remove(text);
};

return (
<span className={cc("rti--tag", className)}>
<span>{text}</span>

{!Array.isArray(text) && <span>{text}</span>}

{Array.isArray(text) && text.map((item, index:number) => {
return <span key={index}>{item}{index < text.length - 1 && '->'}</span>
})}

{openTag && (
<input className={cc("rti--input--tag",classNameInput)} onKeyUp={handleKeyUp} autoFocus={true}/>
)}

{!disabled && (
<button
type="button"
26 changes: 25 additions & 1 deletion stories/tags-input.stories.tsx
Original file line number Diff line number Diff line change
@@ -7,10 +7,13 @@ export default {
};

export const Page = () => {
const [selected, setSelected] = useState(["papaya"]);
const [selected, setSelected] = useState([]);
const [disabled, setDisabled] = useState(false);
const [isEditOnRemove, setisEditOnRemove] = useState(false);

const [multipleValues,setMultipleValues] = useState(false)
const [numberOfValues,setNumberOfValues] = useState(0)

const beforeAddValidate = text => {
if (text.length < 3) {
alert("too short!");
@@ -30,6 +33,8 @@ export const Page = () => {
placeHolder="enter fruits"
disabled={disabled}
isEditOnRemove={isEditOnRemove}
multiValueTags={multipleValues}
numberOfValuesPerTag={numberOfValues}
beforeAddValidate={beforeAddValidate}
/>
<div style={{ marginTop: "2rem" }}>
@@ -50,6 +55,25 @@ export const Page = () => {
</button>
<pre>Keep Words on Backspace: {JSON.stringify(isEditOnRemove)}</pre>
</div>


<div>
<button
onClick={() => setMultipleValues(!multipleValues)}
style={{ marginRight: "2rem" }}
>
Toggle Multiple Values per Tag
</button>
<pre>Multiple values per tag: {JSON.stringify(multipleValues)}</pre>
</div>

<div>

<input placeholder={"Enter number of values"} style={{ marginRight: "2rem" }} type={"number"} onChange={(e) => setNumberOfValues(Number(e.target.value))}/>

<pre>Number of values per tag: {JSON.stringify(numberOfValues)}</pre>
</div>

<div>
<button onClick={() => setSelected(["tangerine"])}>
override value