Skip to content
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

Feature/multiple values per tag #36

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Expand Up @@ -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

Expand Down
66 changes: 48 additions & 18 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -56,6 +62,7 @@ export const TagsInput = ({
}
}, [value]);


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

Expand All @@ -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}
/>
Expand Down
15 changes: 13 additions & 2 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Up @@ -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"
Expand Down
26 changes: 25 additions & 1 deletion stories/tags-input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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!");
Expand All @@ -30,6 +33,8 @@ export const Page = () => {
placeHolder="enter fruits"
disabled={disabled}
isEditOnRemove={isEditOnRemove}
multiValueTags={multipleValues}
numberOfValuesPerTag={numberOfValues}
beforeAddValidate={beforeAddValidate}
/>
<div style={{ marginTop: "2rem" }}>
Expand All @@ -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
Expand Down