react-input-chips 0.3.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/License +2 -2
- package/README.md +7 -7
- package/dist/index.d.ts +7 -1
- package/dist/index.js +65 -18
- package/dist/index.js.map +1 -1
- package/package.json +11 -4
- package/src/components/InputChip.test.tsx +63 -0
- package/src/components/InputChip.tsx +50 -27
- package/src/components/styles.css +12 -6
- package/src/setupTests.ts +1 -0
- package/src/types/index.ts +6 -0
- package/dist/index.css +0 -52
- package/dist/index.css.map +0 -1
package/License
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 [B-Meet]
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "react-input-
|
|
6
|
+
of this software and associated documentation files (the "react-input-chips"), to deal
|
|
7
7
|
in the Software without restriction, including without limitation the rights
|
|
8
8
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
package/README.md
CHANGED
|
@@ -28,15 +28,9 @@ Install react-input-chips with npm
|
|
|
28
28
|
|
|
29
29
|
You just need to import the `<InputChips/>` component in the file you want the **react-input-chip** in and pass the required props that's it. All set!
|
|
30
30
|
|
|
31
|
-
**
|
|
32
|
-
The following is the path to it
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
import "../node_modules/react-input-chips/dist/index.css";
|
|
36
|
-
```
|
|
31
|
+
**Note:** Styles are now automatically injected, so no need to import the CSS file manually!
|
|
37
32
|
|
|
38
33
|
Hers is the simple plain example
|
|
39
|
-
|
|
40
34
|
```javascript
|
|
41
35
|
import { useState } from "react";
|
|
42
36
|
import { InputChips } from "react-input-chips";
|
|
@@ -112,7 +106,13 @@ I have for you here all the props supported as of now. (\* these are required pr
|
|
|
112
106
|
| nextPlaceholder | - | Placeholder after the first chip is created |
|
|
113
107
|
| removeBtnSvg | is an `SVG` which looks like close/X | You can add any HTML element as of now, but it is better to add an `SVG` element |
|
|
114
108
|
| chipStyles | - | You add any styles supported by CSS will be added as inline styles for the chip hence, the highest priority is given to your styles |
|
|
109
|
+
| chipClassName | - | Class name for individual chips |
|
|
115
110
|
| containerStyles | - | You can add the CSS styles for the whole input container itself |
|
|
111
|
+
| containerClassName | - | Class name for the container wrapper |
|
|
112
|
+
| inputStyle | - | Inline styles for the input field |
|
|
113
|
+
| inputClassName | - | Class name for the input field |
|
|
114
|
+
| closeBtnStyle | - | Inline styles for the close button |
|
|
115
|
+
| closeBtnClassName | - | Class name for the close button |
|
|
116
116
|
| errorMsg | - | It's the error message you want to display and expects a string as its value |
|
|
117
117
|
|
|
118
118
|
keysToTriggerChipConversion - Allowed key codes
|
package/dist/index.d.ts
CHANGED
|
@@ -16,8 +16,14 @@ interface TInputChips {
|
|
|
16
16
|
containerStyles?: React.CSSProperties;
|
|
17
17
|
backspaceToRemoveChip?: boolean;
|
|
18
18
|
errorMsg?: string;
|
|
19
|
+
chipClassName?: string;
|
|
20
|
+
inputStyle?: React.CSSProperties;
|
|
21
|
+
inputClassName?: string;
|
|
22
|
+
closeBtnStyle?: React.CSSProperties;
|
|
23
|
+
closeBtnClassName?: string;
|
|
24
|
+
containerClassName?: string;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
declare const InputChips: ({ chips, setChips, inputValue, setInputValue, keysToTriggerChipConversion, validate, disabled, placeholder, nextPlaceholder, removeBtnSvg, chipStyles, containerStyles, backspaceToRemoveChip, errorMsg, }: TInputChips) => react_jsx_runtime.JSX.Element;
|
|
27
|
+
declare const InputChips: ({ chips, setChips, inputValue, setInputValue, keysToTriggerChipConversion, validate, disabled, placeholder, nextPlaceholder, removeBtnSvg, chipStyles, chipClassName, containerStyles, containerClassName, inputStyle, inputClassName, closeBtnStyle, closeBtnClassName, backspaceToRemoveChip, errorMsg, }: TInputChips) => react_jsx_runtime.JSX.Element;
|
|
22
28
|
|
|
23
29
|
export { InputChips };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
// src/components/InputChip.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
// #style-inject:#style-inject
|
|
5
|
+
function styleInject(css, { insertAt } = {}) {
|
|
6
|
+
if (!css || typeof document === "undefined") return;
|
|
7
|
+
const head = document.head || document.getElementsByTagName("head")[0];
|
|
8
|
+
const style = document.createElement("style");
|
|
9
|
+
style.type = "text/css";
|
|
10
|
+
if (insertAt === "top") {
|
|
11
|
+
if (head.firstChild) {
|
|
12
|
+
head.insertBefore(style, head.firstChild);
|
|
13
|
+
} else {
|
|
14
|
+
head.appendChild(style);
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
head.appendChild(style);
|
|
18
|
+
}
|
|
19
|
+
if (style.styleSheet) {
|
|
20
|
+
style.styleSheet.cssText = css;
|
|
21
|
+
} else {
|
|
22
|
+
style.appendChild(document.createTextNode(css));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/components/styles.css
|
|
27
|
+
styleInject(".ric-chip-input-wrapper {\n gap: 4px;\n flex: 2;\n padding: 5px;\n overflow: auto;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n border: 1px solid #BBBBBB;\n border-radius: 4px;\n max-width: 650px;\n min-width: 300px;\n transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;\n &.ric-chip-input-wrapper-focus {\n border-color: #4a90e2;\n box-shadow: 0 0 0 1px #4a90e2;\n }\n .ric-chip {\n background: #e5e5e5;\n border-radius: 4px;\n color: #333333;\n display: flex;\n gap: 0.5rem;\n align-items: center;\n justify-content: space-between;\n max-width: max-content;\n padding: 6px;\n }\n .ric-chip-input {\n border: none;\n outline: none;\n flex: 1;\n padding: 0 8px;\n height: 30px;\n }\n .ric-closeBtn {\n outline: none;\n border: none;\n background: none;\n height: 18px;\n width: 18px;\n object-fit: cover;\n cursor: pointer;\n display: grid;\n place-items: center;\n }\n}\n.ric-chip-input-wrapper-error {\n border: 2px solid red;\n}\n.ric-error-msg-wrapper {\n margin: 0;\n font-size: 12px;\n color: red;\n}\n");
|
|
28
|
+
|
|
1
29
|
// src/components/InputChip.tsx
|
|
2
30
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
31
|
var InputChips = ({
|
|
@@ -12,10 +40,18 @@ var InputChips = ({
|
|
|
12
40
|
nextPlaceholder,
|
|
13
41
|
removeBtnSvg,
|
|
14
42
|
chipStyles,
|
|
43
|
+
chipClassName,
|
|
15
44
|
containerStyles,
|
|
45
|
+
containerClassName,
|
|
46
|
+
inputStyle,
|
|
47
|
+
inputClassName,
|
|
48
|
+
closeBtnStyle,
|
|
49
|
+
closeBtnClassName,
|
|
16
50
|
backspaceToRemoveChip = false,
|
|
17
51
|
errorMsg
|
|
18
52
|
}) => {
|
|
53
|
+
const inputRef = React.useRef(null);
|
|
54
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
19
55
|
const handleInputChange = (e) => {
|
|
20
56
|
const value = e.target.value;
|
|
21
57
|
if (keysToTriggerChipConversion.includes("Space")) {
|
|
@@ -25,12 +61,14 @@ var InputChips = ({
|
|
|
25
61
|
}
|
|
26
62
|
};
|
|
27
63
|
const handleInputKeyDown = (e) => {
|
|
28
|
-
if (backspaceToRemoveChip && e.key === "Backspace" && inputValue.trim() === "") {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
if (backspaceToRemoveChip && e.key === "Backspace" && inputValue.trim() === "" && chips.length > 0) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
const updatedChips = [...chips];
|
|
67
|
+
updatedChips.pop();
|
|
68
|
+
setChips(updatedChips);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (e.key === "ArrowLeft" && inputValue.trim() === "" && chips.length > 0) {
|
|
34
72
|
}
|
|
35
73
|
const isKeyAllowed = (key) => {
|
|
36
74
|
return keysToTriggerChipConversion.includes(
|
|
@@ -40,12 +78,14 @@ var InputChips = ({
|
|
|
40
78
|
if (isKeyAllowed(e.code) && (!validate || validate())) {
|
|
41
79
|
let chip = inputValue.trim();
|
|
42
80
|
if (keysToTriggerChipConversion.some((key) => key.length === 1)) {
|
|
43
|
-
const regex =
|
|
44
|
-
|
|
45
|
-
(key) => key.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
81
|
+
const regex = React.useMemo(() => {
|
|
82
|
+
return new RegExp(
|
|
83
|
+
`[${keysToTriggerChipConversion.filter((key) => key.length === 1).map(
|
|
84
|
+
(key) => key.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")
|
|
85
|
+
).join("")}]`,
|
|
86
|
+
"g"
|
|
87
|
+
);
|
|
88
|
+
}, [keysToTriggerChipConversion]);
|
|
49
89
|
chip = chip.replace(regex, "");
|
|
50
90
|
}
|
|
51
91
|
if (chip) {
|
|
@@ -66,14 +106,15 @@ var InputChips = ({
|
|
|
66
106
|
/* @__PURE__ */ jsxs(
|
|
67
107
|
"section",
|
|
68
108
|
{
|
|
69
|
-
className: errorMsg?.length ? "chip-input-wrapper chip-input-wrapper-error" : "chip-input-wrapper"
|
|
109
|
+
className: `${errorMsg?.length ? "ric-chip-input-wrapper ric-chip-input-wrapper-error" : "ric-chip-input-wrapper"} ${isFocused ? "ric-chip-input-wrapper-focus" : ""} ${containerClassName || ""}`,
|
|
70
110
|
style: containerStyles ?? {},
|
|
111
|
+
onClick: () => inputRef.current?.focus(),
|
|
71
112
|
children: [
|
|
72
113
|
chips.map((chip, index) => /* @__PURE__ */ jsxs(
|
|
73
114
|
"div",
|
|
74
115
|
{
|
|
75
116
|
"data-testid": `input-value-chip-${index}`,
|
|
76
|
-
className:
|
|
117
|
+
className: `ric-chip ${chipClassName || ""}`,
|
|
77
118
|
style: chipStyles ?? {},
|
|
78
119
|
children: [
|
|
79
120
|
chip,
|
|
@@ -81,9 +122,11 @@ var InputChips = ({
|
|
|
81
122
|
"button",
|
|
82
123
|
{
|
|
83
124
|
type: "button",
|
|
84
|
-
className:
|
|
125
|
+
className: `ric-closeBtn ${closeBtnClassName || ""}`,
|
|
126
|
+
style: closeBtnStyle ?? {},
|
|
85
127
|
"data-testid": `remove-chip-btn-${index}`,
|
|
86
128
|
onClick: (e) => removeChip(e, chip + index),
|
|
129
|
+
"aria-label": `Remove chip ${chip}`,
|
|
87
130
|
children: removeBtnSvg || /* @__PURE__ */ jsxs(
|
|
88
131
|
"svg",
|
|
89
132
|
{
|
|
@@ -107,19 +150,23 @@ var InputChips = ({
|
|
|
107
150
|
/* @__PURE__ */ jsx(
|
|
108
151
|
"input",
|
|
109
152
|
{
|
|
110
|
-
|
|
153
|
+
ref: inputRef,
|
|
154
|
+
className: `ric-chip-input ${inputClassName || ""}`,
|
|
155
|
+
style: inputStyle ?? {},
|
|
111
156
|
type: "text",
|
|
112
157
|
placeholder: disabled ? "" : chips.length ? nextPlaceholder : placeholder,
|
|
113
158
|
value: inputValue,
|
|
114
159
|
onChange: handleInputChange,
|
|
115
160
|
onKeyDown: handleInputKeyDown,
|
|
116
|
-
disabled
|
|
161
|
+
disabled,
|
|
162
|
+
onFocus: () => setIsFocused(true),
|
|
163
|
+
onBlur: () => setIsFocused(false)
|
|
117
164
|
}
|
|
118
165
|
)
|
|
119
166
|
]
|
|
120
167
|
}
|
|
121
168
|
),
|
|
122
|
-
errorMsg?.length && /* @__PURE__ */ jsx("p", { className: "error-msg-wrapper", children: errorMsg })
|
|
169
|
+
errorMsg?.length && /* @__PURE__ */ jsx("p", { className: "ric-error-msg-wrapper", children: errorMsg })
|
|
123
170
|
] });
|
|
124
171
|
};
|
|
125
172
|
var InputChip_default = InputChips;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/InputChip.tsx"],"sourcesContent":["import React from 'react';\nimport './styles.css';\nimport {TInputChips, AllowedKeys} from '../types';\n\nconst InputChips = ({\n chips,\n setChips,\n inputValue,\n setInputValue,\n keysToTriggerChipConversion = ['Enter', 'Comma'],\n validate,\n disabled = false,\n placeholder,\n nextPlaceholder,\n removeBtnSvg,\n chipStyles,\n containerStyles,\n backspaceToRemoveChip = false,\n errorMsg,\n}: TInputChips) => {\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value;\n\n if (keysToTriggerChipConversion.includes('Space')) {\n setInputValue(value.trim());\n } else {\n setInputValue(value);\n }\n };\n\n const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (\n backspaceToRemoveChip &&\n e.key === 'Backspace' &&\n inputValue.trim() === ''\n ) {\n setChips((prevState) => {\n const updatedChips = [...prevState];\n updatedChips.pop();\n return updatedChips;\n });\n }\n\n const isKeyAllowed = (key: string): key is AllowedKeys => {\n return (keysToTriggerChipConversion as AllowedKeys[]).includes(\n key as AllowedKeys\n );\n };\n\n if (isKeyAllowed(e.code) && (!validate || validate())) {\n let chip = inputValue.trim();\n\n if (keysToTriggerChipConversion.some((key) => key.length === 1)) {\n const regex = new RegExp(\n `[${keysToTriggerChipConversion\n .filter((key) => key.length === 1)\n .map((key) =>\n key.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')\n )\n .join('')}]`,\n 'g'\n );\n chip = chip.replace(regex, '');\n }\n\n if (chip) {\n setChips([...chips, chip]);\n setInputValue('');\n }\n\n e.preventDefault();\n }\n };\n\n const removeChip = (\n e: React.MouseEvent<HTMLButtonElement, MouseEvent>,\n chipToRemove: string\n ) => {\n e.preventDefault();\n const filteredChips = chips.filter(\n (chip, index) => chip + index !== chipToRemove\n );\n setChips(filteredChips);\n };\n\n return (\n <>\n <section\n className={\n errorMsg?.length\n ? 'chip-input-wrapper chip-input-wrapper-error'\n : 'chip-input-wrapper'\n }\n style={containerStyles ?? {}}\n >\n {chips.map((chip, index) => (\n <div\n key={chip + index}\n data-testid={`input-value-chip-${index}`}\n className=\"chip\"\n style={chipStyles ?? {}}\n >\n {chip}\n <button\n type=\"button\"\n className=\"closeBtn\"\n data-testid={`remove-chip-btn-${index}`}\n onClick={(e) => removeChip(e, chip + index)}\n >\n {removeBtnSvg || (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 24 24\"\n height={15}\n >\n <path fill=\"none\" d=\"M0 0h24v24H0z\"></path>\n <path d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"></path>\n </svg>\n )}\n </button>\n </div>\n ))}\n <input\n className=\"chip-input\"\n type=\"text\"\n placeholder={\n disabled\n ? ''\n : chips.length\n ? nextPlaceholder\n : placeholder\n }\n value={inputValue}\n onChange={handleInputChange}\n onKeyDown={handleInputKeyDown}\n disabled={disabled}\n />\n </section>\n {errorMsg?.length && (\n <p className=\"error-msg-wrapper\">{errorMsg}</p>\n )}\n </>\n );\n};\n\nexport default InputChips;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/components/InputChip.tsx","#style-inject:#style-inject","../src/components/styles.css"],"sourcesContent":["import React from 'react';\nimport './styles.css';\nimport { TInputChips, AllowedKeys } from '../types';\n\nconst InputChips = ({\n chips,\n setChips,\n inputValue,\n setInputValue,\n keysToTriggerChipConversion = ['Enter', 'Comma'],\n validate,\n disabled = false,\n placeholder,\n nextPlaceholder,\n removeBtnSvg,\n chipStyles,\n chipClassName,\n containerStyles,\n containerClassName,\n inputStyle,\n inputClassName,\n closeBtnStyle,\n closeBtnClassName,\n backspaceToRemoveChip = false,\n errorMsg,\n}: TInputChips) => {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const [isFocused, setIsFocused] = React.useState(false);\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value;\n\n if (keysToTriggerChipConversion.includes('Space')) {\n setInputValue(value.trim());\n } else {\n setInputValue(value);\n }\n };\n\n const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (\n backspaceToRemoveChip &&\n e.key === 'Backspace' &&\n inputValue.trim() === '' &&\n chips.length > 0\n ) {\n e.preventDefault();\n const updatedChips = [...chips];\n updatedChips.pop();\n setChips(updatedChips);\n return;\n }\n\n if (e.key === 'ArrowLeft' && inputValue.trim() === '' && chips.length > 0) {\n // Logic to select last chip could be added here, but for now we are just cleaning up\n }\n\n const isKeyAllowed = (key: string): key is AllowedKeys => {\n return (keysToTriggerChipConversion as AllowedKeys[]).includes(\n key as AllowedKeys\n );\n };\n\n if (isKeyAllowed(e.code) && (!validate || validate())) {\n let chip = inputValue.trim();\n\n if (keysToTriggerChipConversion.some((key) => key.length === 1)) {\n const regex = React.useMemo(() => {\n return new RegExp(\n `[${keysToTriggerChipConversion\n .filter((key) => key.length === 1)\n .map((key) =>\n key.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')\n )\n .join('')}]`,\n 'g'\n );\n }, [keysToTriggerChipConversion]);\n\n chip = chip.replace(regex, '');\n }\n\n if (chip) {\n setChips([...chips, chip]);\n setInputValue('');\n }\n\n e.preventDefault();\n }\n };\n\n const removeChip = (\n e: React.MouseEvent<HTMLButtonElement, MouseEvent>,\n chipToRemove: string\n ) => {\n e.preventDefault();\n const filteredChips = chips.filter(\n (chip, index) => chip + index !== chipToRemove\n );\n setChips(filteredChips);\n };\n\n return (\n <>\n <section\n className={`${errorMsg?.length\n ? 'ric-chip-input-wrapper ric-chip-input-wrapper-error'\n : 'ric-chip-input-wrapper'\n } ${isFocused ? 'ric-chip-input-wrapper-focus' : ''} ${containerClassName || ''}`}\n style={containerStyles ?? {}}\n onClick={() => inputRef.current?.focus()}\n >\n {chips.map((chip, index) => (\n <div\n key={chip + index}\n data-testid={`input-value-chip-${index}`}\n className={`ric-chip ${chipClassName || ''}`}\n style={chipStyles ?? {}}\n >\n {chip}\n <button\n type=\"button\"\n className={`ric-closeBtn ${closeBtnClassName || ''}`}\n style={closeBtnStyle ?? {}}\n data-testid={`remove-chip-btn-${index}`}\n onClick={(e) => removeChip(e, chip + index)}\n aria-label={`Remove chip ${chip}`}\n >\n {removeBtnSvg || (\n <svg\n stroke=\"currentColor\"\n fill=\"currentColor\"\n strokeWidth=\"0\"\n viewBox=\"0 0 24 24\"\n height={15}\n >\n <path fill=\"none\" d=\"M0 0h24v24H0z\"></path>\n <path d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"></path>\n </svg>\n )}\n </button>\n </div>\n ))}\n <input\n ref={inputRef}\n className={`ric-chip-input ${inputClassName || ''}`}\n style={inputStyle ?? {}}\n type=\"text\"\n placeholder={\n disabled\n ? ''\n : chips.length\n ? nextPlaceholder\n : placeholder\n }\n value={inputValue}\n onChange={handleInputChange}\n onKeyDown={handleInputKeyDown}\n disabled={disabled}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n />\n </section>\n {errorMsg?.length && (\n <p className=\"ric-error-msg-wrapper\">{errorMsg}</p>\n )}\n </>\n );\n};\n\nexport default InputChips;\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".ric-chip-input-wrapper {\\n gap: 4px;\\n flex: 2;\\n padding: 5px;\\n overflow: auto;\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n border: 1px solid #BBBBBB;\\n border-radius: 4px;\\n max-width: 650px;\\n min-width: 300px;\\n transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;\\n &.ric-chip-input-wrapper-focus {\\n border-color: #4a90e2;\\n box-shadow: 0 0 0 1px #4a90e2;\\n }\\n .ric-chip {\\n background: #e5e5e5;\\n border-radius: 4px;\\n color: #333333;\\n display: flex;\\n gap: 0.5rem;\\n align-items: center;\\n justify-content: space-between;\\n max-width: max-content;\\n padding: 6px;\\n }\\n .ric-chip-input {\\n border: none;\\n outline: none;\\n flex: 1;\\n padding: 0 8px;\\n height: 30px;\\n }\\n .ric-closeBtn {\\n outline: none;\\n border: none;\\n background: none;\\n height: 18px;\\n width: 18px;\\n object-fit: cover;\\n cursor: pointer;\\n display: grid;\\n place-items: center;\\n }\\n}\\n.ric-chip-input-wrapper-error {\\n border: 2px solid red;\\n}\\n.ric-error-msg-wrapper {\\n margin: 0;\\n font-size: 12px;\\n color: red;\\n}\\n\")"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-input-chips",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"build": "tsup",
|
|
48
48
|
"lint": "eslint .",
|
|
49
49
|
"preview": "vite preview",
|
|
50
|
-
"bundle": "tsup src/index.ts"
|
|
50
|
+
"bundle": "tsup src/index.ts",
|
|
51
|
+
"test": "vitest"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"react": "^18.3.1",
|
|
@@ -55,6 +56,10 @@
|
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@eslint/js": "^9.13.0",
|
|
59
|
+
"@testing-library/dom": "^10.4.1",
|
|
60
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
61
|
+
"@testing-library/react": "^16.3.2",
|
|
62
|
+
"@testing-library/user-event": "^14.6.1",
|
|
58
63
|
"@types/react": "^18.3.12",
|
|
59
64
|
"@types/react-dom": "^18.3.1",
|
|
60
65
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
@@ -62,9 +67,11 @@
|
|
|
62
67
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
63
68
|
"eslint-plugin-react-refresh": "^0.4.14",
|
|
64
69
|
"globals": "^15.11.0",
|
|
70
|
+
"jsdom": "^28.0.0",
|
|
65
71
|
"tsup": "^8.3.5",
|
|
66
72
|
"typescript": "~5.6.2",
|
|
67
73
|
"typescript-eslint": "^8.11.0",
|
|
68
|
-
"vite": "^5.4.10"
|
|
74
|
+
"vite": "^5.4.10",
|
|
75
|
+
"vitest": "^4.0.18"
|
|
69
76
|
}
|
|
70
|
-
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
+
import InputChips from './InputChip';
|
|
6
|
+
|
|
7
|
+
// Wrapper component to manage state for testing
|
|
8
|
+
const TestWrapper = (props: any) => {
|
|
9
|
+
const [chips, setChips] = useState<string[]>(props.initialChips || []);
|
|
10
|
+
const [inputValue, setInputValue] = useState('');
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<InputChips
|
|
14
|
+
chips={chips}
|
|
15
|
+
setChips={setChips}
|
|
16
|
+
inputValue={inputValue}
|
|
17
|
+
setInputValue={setInputValue}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('InputChips', () => {
|
|
24
|
+
it('renders correctly', () => {
|
|
25
|
+
render(<TestWrapper />);
|
|
26
|
+
const input = screen.getByRole('textbox');
|
|
27
|
+
expect(input).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('adds a chip when Enter is pressed', () => {
|
|
31
|
+
render(<TestWrapper />);
|
|
32
|
+
const input = screen.getByRole('textbox');
|
|
33
|
+
fireEvent.change(input, { target: { value: 'test-chip' } });
|
|
34
|
+
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
|
35
|
+
|
|
36
|
+
expect(screen.getByText('test-chip')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('removes a chip when delete button is clicked', () => {
|
|
40
|
+
render(<TestWrapper initialChips={['chip1']} />);
|
|
41
|
+
const deleteBtn = screen.getByTestId('remove-chip-btn-0');
|
|
42
|
+
fireEvent.click(deleteBtn);
|
|
43
|
+
expect(screen.queryByText('chip1')).not.toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('removes the last chip on Backspace when input is empty', () => {
|
|
47
|
+
render(<TestWrapper initialChips={['chip1']} backspaceToRemoveChip={true} />);
|
|
48
|
+
const input = screen.getByRole('textbox');
|
|
49
|
+
fireEvent.keyDown(input, { key: 'Backspace', code: 'Backspace' });
|
|
50
|
+
expect(screen.queryByText('chip1')).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('validates input before adding chip', () => {
|
|
54
|
+
const validate = vi.fn().mockReturnValue(false);
|
|
55
|
+
render(<TestWrapper validate={validate} />);
|
|
56
|
+
const input = screen.getByRole('textbox');
|
|
57
|
+
fireEvent.change(input, { target: { value: 'invalid' } });
|
|
58
|
+
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
|
|
59
|
+
|
|
60
|
+
expect(screen.queryByText('invalid')).not.toBeInTheDocument();
|
|
61
|
+
expect(validate).toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import './styles.css';
|
|
3
|
-
import {TInputChips, AllowedKeys} from '../types';
|
|
3
|
+
import { TInputChips, AllowedKeys } from '../types';
|
|
4
4
|
|
|
5
5
|
const InputChips = ({
|
|
6
6
|
chips,
|
|
@@ -14,10 +14,19 @@ const InputChips = ({
|
|
|
14
14
|
nextPlaceholder,
|
|
15
15
|
removeBtnSvg,
|
|
16
16
|
chipStyles,
|
|
17
|
+
chipClassName,
|
|
17
18
|
containerStyles,
|
|
19
|
+
containerClassName,
|
|
20
|
+
inputStyle,
|
|
21
|
+
inputClassName,
|
|
22
|
+
closeBtnStyle,
|
|
23
|
+
closeBtnClassName,
|
|
18
24
|
backspaceToRemoveChip = false,
|
|
19
25
|
errorMsg,
|
|
20
26
|
}: TInputChips) => {
|
|
27
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
28
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
29
|
+
|
|
21
30
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
22
31
|
const value = e.target.value;
|
|
23
32
|
|
|
@@ -32,13 +41,18 @@ const InputChips = ({
|
|
|
32
41
|
if (
|
|
33
42
|
backspaceToRemoveChip &&
|
|
34
43
|
e.key === 'Backspace' &&
|
|
35
|
-
inputValue.trim() === ''
|
|
44
|
+
inputValue.trim() === '' &&
|
|
45
|
+
chips.length > 0
|
|
36
46
|
) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
const updatedChips = [...chips];
|
|
49
|
+
updatedChips.pop();
|
|
50
|
+
setChips(updatedChips);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (e.key === 'ArrowLeft' && inputValue.trim() === '' && chips.length > 0) {
|
|
55
|
+
// Logic to select last chip could be added here, but for now we are just cleaning up
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
const isKeyAllowed = (key: string): key is AllowedKeys => {
|
|
@@ -51,15 +65,18 @@ const InputChips = ({
|
|
|
51
65
|
let chip = inputValue.trim();
|
|
52
66
|
|
|
53
67
|
if (keysToTriggerChipConversion.some((key) => key.length === 1)) {
|
|
54
|
-
const regex =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
const regex = React.useMemo(() => {
|
|
69
|
+
return new RegExp(
|
|
70
|
+
`[${keysToTriggerChipConversion
|
|
71
|
+
.filter((key) => key.length === 1)
|
|
72
|
+
.map((key) =>
|
|
73
|
+
key.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
|
74
|
+
)
|
|
75
|
+
.join('')}]`,
|
|
76
|
+
'g'
|
|
77
|
+
);
|
|
78
|
+
}, [keysToTriggerChipConversion]);
|
|
79
|
+
|
|
63
80
|
chip = chip.replace(regex, '');
|
|
64
81
|
}
|
|
65
82
|
|
|
@@ -86,26 +103,28 @@ const InputChips = ({
|
|
|
86
103
|
return (
|
|
87
104
|
<>
|
|
88
105
|
<section
|
|
89
|
-
className={
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
106
|
+
className={`${errorMsg?.length
|
|
107
|
+
? 'ric-chip-input-wrapper ric-chip-input-wrapper-error'
|
|
108
|
+
: 'ric-chip-input-wrapper'
|
|
109
|
+
} ${isFocused ? 'ric-chip-input-wrapper-focus' : ''} ${containerClassName || ''}`}
|
|
94
110
|
style={containerStyles ?? {}}
|
|
111
|
+
onClick={() => inputRef.current?.focus()}
|
|
95
112
|
>
|
|
96
113
|
{chips.map((chip, index) => (
|
|
97
114
|
<div
|
|
98
115
|
key={chip + index}
|
|
99
116
|
data-testid={`input-value-chip-${index}`}
|
|
100
|
-
className=
|
|
117
|
+
className={`ric-chip ${chipClassName || ''}`}
|
|
101
118
|
style={chipStyles ?? {}}
|
|
102
119
|
>
|
|
103
120
|
{chip}
|
|
104
121
|
<button
|
|
105
122
|
type="button"
|
|
106
|
-
className=
|
|
123
|
+
className={`ric-closeBtn ${closeBtnClassName || ''}`}
|
|
124
|
+
style={closeBtnStyle ?? {}}
|
|
107
125
|
data-testid={`remove-chip-btn-${index}`}
|
|
108
126
|
onClick={(e) => removeChip(e, chip + index)}
|
|
127
|
+
aria-label={`Remove chip ${chip}`}
|
|
109
128
|
>
|
|
110
129
|
{removeBtnSvg || (
|
|
111
130
|
<svg
|
|
@@ -123,23 +142,27 @@ const InputChips = ({
|
|
|
123
142
|
</div>
|
|
124
143
|
))}
|
|
125
144
|
<input
|
|
126
|
-
|
|
145
|
+
ref={inputRef}
|
|
146
|
+
className={`ric-chip-input ${inputClassName || ''}`}
|
|
147
|
+
style={inputStyle ?? {}}
|
|
127
148
|
type="text"
|
|
128
149
|
placeholder={
|
|
129
150
|
disabled
|
|
130
151
|
? ''
|
|
131
152
|
: chips.length
|
|
132
|
-
|
|
133
|
-
|
|
153
|
+
? nextPlaceholder
|
|
154
|
+
: placeholder
|
|
134
155
|
}
|
|
135
156
|
value={inputValue}
|
|
136
157
|
onChange={handleInputChange}
|
|
137
158
|
onKeyDown={handleInputKeyDown}
|
|
138
159
|
disabled={disabled}
|
|
160
|
+
onFocus={() => setIsFocused(true)}
|
|
161
|
+
onBlur={() => setIsFocused(false)}
|
|
139
162
|
/>
|
|
140
163
|
</section>
|
|
141
164
|
{errorMsg?.length && (
|
|
142
|
-
<p className="error-msg-wrapper">{errorMsg}</p>
|
|
165
|
+
<p className="ric-error-msg-wrapper">{errorMsg}</p>
|
|
143
166
|
)}
|
|
144
167
|
</>
|
|
145
168
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.chip-input-wrapper {
|
|
1
|
+
.ric-chip-input-wrapper {
|
|
2
2
|
gap: 4px;
|
|
3
3
|
flex: 2;
|
|
4
4
|
padding: 5px;
|
|
@@ -10,8 +10,14 @@
|
|
|
10
10
|
border-radius: 4px;
|
|
11
11
|
max-width: 650px;
|
|
12
12
|
min-width: 300px;
|
|
13
|
+
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
&.ric-chip-input-wrapper-focus {
|
|
16
|
+
border-color: #4a90e2;
|
|
17
|
+
box-shadow: 0 0 0 1px #4a90e2;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ric-chip {
|
|
15
21
|
background: #e5e5e5;
|
|
16
22
|
border-radius: 4px;
|
|
17
23
|
color: #333333;
|
|
@@ -23,7 +29,7 @@
|
|
|
23
29
|
padding: 6px;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
.chip-input {
|
|
32
|
+
.ric-chip-input {
|
|
27
33
|
border: none;
|
|
28
34
|
outline: none;
|
|
29
35
|
flex: 1;
|
|
@@ -31,7 +37,7 @@
|
|
|
31
37
|
height: 30px;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
.closeBtn {
|
|
40
|
+
.ric-closeBtn {
|
|
35
41
|
outline: none;
|
|
36
42
|
border: none;
|
|
37
43
|
background: none;
|
|
@@ -44,11 +50,11 @@
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
.chip-input-wrapper-error {
|
|
53
|
+
.ric-chip-input-wrapper-error {
|
|
48
54
|
border: 2px solid red
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
.error-msg-wrapper {
|
|
57
|
+
.ric-error-msg-wrapper {
|
|
52
58
|
margin: 0;
|
|
53
59
|
font-size: 12px;
|
|
54
60
|
color: red
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
package/src/types/index.ts
CHANGED
|
@@ -33,4 +33,10 @@ export interface TInputChips {
|
|
|
33
33
|
containerStyles?: React.CSSProperties;
|
|
34
34
|
backspaceToRemoveChip?: boolean;
|
|
35
35
|
errorMsg?: string;
|
|
36
|
+
chipClassName?: string;
|
|
37
|
+
inputStyle?: React.CSSProperties;
|
|
38
|
+
inputClassName?: string;
|
|
39
|
+
closeBtnStyle?: React.CSSProperties;
|
|
40
|
+
closeBtnClassName?: string;
|
|
41
|
+
containerClassName?: string;
|
|
36
42
|
}
|
package/dist/index.css
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/* src/components/styles.css */
|
|
2
|
-
.chip-input-wrapper {
|
|
3
|
-
gap: 4px;
|
|
4
|
-
flex: 2;
|
|
5
|
-
padding: 5px;
|
|
6
|
-
overflow: auto;
|
|
7
|
-
display: flex;
|
|
8
|
-
flex-wrap: wrap;
|
|
9
|
-
align-items: center;
|
|
10
|
-
border: 1px solid #BBBBBB;
|
|
11
|
-
border-radius: 4px;
|
|
12
|
-
max-width: 650px;
|
|
13
|
-
min-width: 300px;
|
|
14
|
-
.chip {
|
|
15
|
-
background: #e5e5e5;
|
|
16
|
-
border-radius: 4px;
|
|
17
|
-
color: #333333;
|
|
18
|
-
display: flex;
|
|
19
|
-
gap: 0.5rem;
|
|
20
|
-
align-items: center;
|
|
21
|
-
justify-content: space-between;
|
|
22
|
-
max-width: max-content;
|
|
23
|
-
padding: 6px;
|
|
24
|
-
}
|
|
25
|
-
.chip-input {
|
|
26
|
-
border: none;
|
|
27
|
-
outline: none;
|
|
28
|
-
flex: 1;
|
|
29
|
-
padding: 0 8px;
|
|
30
|
-
height: 30px;
|
|
31
|
-
}
|
|
32
|
-
.closeBtn {
|
|
33
|
-
outline: none;
|
|
34
|
-
border: none;
|
|
35
|
-
background: none;
|
|
36
|
-
height: 18px;
|
|
37
|
-
width: 18px;
|
|
38
|
-
object-fit: cover;
|
|
39
|
-
cursor: pointer;
|
|
40
|
-
display: grid;
|
|
41
|
-
place-items: center;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
.chip-input-wrapper-error {
|
|
45
|
-
border: 2px solid red;
|
|
46
|
-
}
|
|
47
|
-
.error-msg-wrapper {
|
|
48
|
-
margin: 0;
|
|
49
|
-
font-size: 12px;
|
|
50
|
-
color: red;
|
|
51
|
-
}
|
|
52
|
-
/*# sourceMappingURL=index.css.map */
|
package/dist/index.css.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/styles.css"],"sourcesContent":[".chip-input-wrapper {\n gap: 4px;\n flex: 2;\n padding: 5px;\n overflow: auto;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n border: 1px solid #BBBBBB;\n border-radius: 4px;\n max-width: 650px;\n min-width: 300px;\n\n .chip {\n background: #e5e5e5;\n border-radius: 4px;\n color: #333333;\n display: flex;\n gap: 0.5rem;\n align-items: center;\n justify-content: space-between;\n max-width: max-content;\n padding: 6px;\n }\n\n .chip-input {\n border: none;\n outline: none;\n flex: 1;\n padding: 0 8px;\n height: 30px;\n }\n\n .closeBtn {\n outline: none;\n border: none;\n background: none;\n height: 18px;\n width: 18px;\n object-fit: cover;\n cursor: pointer;\n display: grid;\n place-items: center;\n }\n}\n\n.chip-input-wrapper-error {\n border: 2px solid red\n}\n\n.error-msg-wrapper {\n margin: 0;\n font-size: 12px;\n color: red\n}"],"mappings":";AAAA,CAAC;AACE,OAAK;AACL,QAAM;AACN,WAAS;AACT,YAAU;AACV,WAAS;AACT,aAAW;AACX,eAAa;AACb,UAAQ,IAAI,MAAM;AAClB,iBAAe;AACf,aAAW;AACX,aAAW;AAEX,GAAC;AACE,gBAAY;AACZ,mBAAe;AACf,WAAO;AACP,aAAS;AACT,SAAK;AACL,iBAAa;AACb,qBAAiB;AACjB,eAAW;AACX,aAAS;AACZ;AAEA,GAAC;AACE,YAAQ;AACR,aAAS;AACT,UAAM;AACN,aAAS,EAAE;AACX,YAAQ;AACX;AAEA,GAAC;AACE,aAAS;AACT,YAAQ;AACR,gBAAY;AACZ,YAAQ;AACR,WAAO;AACP,gBAAY;AACZ,YAAQ;AACR,aAAS;AACT,iBAAa;AAChB;AACH;AAEA,CAAC;AACE,UAAQ,IAAI,MAAM;AACrB;AAEA,CAAC;AACE,UAAQ;AACR,aAAW;AACX,SAAO;AACV;","names":[]}
|