├── .nvmrc ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── .editorconfig ├── .storybook ├── preview.js ├── preview-head.html └── main.js ├── src ├── use-did-update-effect.tsx ├── classnames.tsx ├── tag.tsx ├── styles.css └── index.tsx ├── tsconfig.json ├── LICENSE ├── stories └── tags-input.stories.tsx ├── package.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: harshzalavadiya 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | storybook-static 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | } -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /src/use-did-update-effect.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | export function useDidUpdateEffect(fn, inputs) { 4 | const didMountRef = useRef(false); 5 | 6 | useEffect(() => { 7 | if (didMountRef.current) fn(); 8 | else didMountRef.current = true; 9 | }, inputs); 10 | } -------------------------------------------------------------------------------- /src/classnames.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * A minimal utility to combine classes 3 | * 4 | * @export 5 | * @param {(string[] | string | undefined)} obj 6 | * @returns {string} 7 | */ 8 | export default function cc(...obj: (string | number | undefined)[]): string { 9 | return obj.filter(c => c).join(" "); 10 | } 11 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../stories/**/*.stories.mdx", 4 | "../stories/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/addon-interactions" 10 | ], 11 | "framework": "@storybook/react" 12 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "noImplicitAny": false, 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./src", 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "moduleResolution": "node", 17 | "baseUrl": "./", 18 | "paths": { 19 | "@": ["./"], 20 | "*": ["src/*", "node_modules/*"] 21 | }, 22 | "jsx": "react", 23 | "esModuleInterop": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tag.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cc from "./classnames"; 3 | 4 | interface TagProps { 5 | text: string; 6 | remove: any; 7 | disabled?: boolean; 8 | className?: string; 9 | } 10 | 11 | export default function Tag({ text, remove, disabled, className }: TagProps) { 12 | const handleOnRemove = e => { 13 | e.stopPropagation(); 14 | remove(text); 15 | }; 16 | 17 | return ( 18 | 19 | {text} 20 | {!disabled && ( 21 | 28 | )} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | 7 | steps: 8 | - name: Begin CI... 9 | uses: actions/checkout@v2 10 | 11 | - name: Use Node 16 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: 16.x 15 | 16 | - name: Use cached node_modules 17 | uses: actions/cache@v1 18 | with: 19 | path: node_modules 20 | key: nodeModules-${{ hashFiles('**/yarn.lock') }} 21 | restore-keys: | 22 | nodeModules- 23 | 24 | - name: Install dependencies 25 | run: yarn install --frozen-lockfile 26 | env: 27 | CI: true 28 | 29 | - name: Build 30 | run: yarn build 31 | env: 32 | CI: true 33 | 34 | - name: Publish 35 | if: startsWith(github.ref, 'refs/tags/') 36 | run: echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc && npm publish --access public 37 | env: 38 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Harsh Zalavadiya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | .rti--container * { 2 | box-sizing: border-box; 3 | transition: all 0.2s ease; 4 | } 5 | 6 | .rti--container { 7 | --rti-bg: #fff; 8 | --rti-border: #ccc; 9 | --rti-main: #3182ce; 10 | --rti-radius: 0.375rem; 11 | --rti-s: 0.5rem; 12 | --rti-tag: #edf2f7; 13 | --rti-tag-remove: #e53e3e; 14 | --rti-tag-padding: 0.15rem 0.25rem; 15 | 16 | /* Container Styles */ 17 | align-items: center; 18 | background: var(--rti-bg); 19 | border: 1px solid var(--rti-border); 20 | border-radius: var(--rti-radius); 21 | display: flex; 22 | flex-wrap: wrap; 23 | gap: var(--rti-s); 24 | line-height: 1.4; 25 | padding: var(--rti-s); 26 | } 27 | 28 | .rti--container:focus-within { 29 | border-color: var(--rti-main); 30 | box-shadow: var(--rti-main) 0px 0px 0px 1px; 31 | } 32 | 33 | .rti--input { 34 | border: 0; 35 | outline: 0; 36 | font-size: inherit; 37 | line-height: inherit; 38 | width: 50%; 39 | } 40 | 41 | .rti--tag { 42 | align-items: center; 43 | background: var(--rti-tag); 44 | border-radius: var(--rti-radius); 45 | display: inline-flex; 46 | justify-content: center; 47 | padding: var(--rti-tag-padding); 48 | } 49 | 50 | .rti--tag button { 51 | background: none; 52 | border: 0; 53 | border-radius: 50%; 54 | cursor: pointer; 55 | line-height: inherit; 56 | padding: 0 var(--rti-s); 57 | } 58 | 59 | .rti--tag button:hover { 60 | color: var(--rti-tag-remove); 61 | } -------------------------------------------------------------------------------- /stories/tags-input.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { TagsInput } from "../src"; 4 | 5 | export default { 6 | title: "Tags Input", 7 | }; 8 | 9 | export const Page = () => { 10 | const [selected, setSelected] = useState(["papaya"]); 11 | const [disabled, setDisabled] = useState(false); 12 | const [isEditOnRemove, setisEditOnRemove] = useState(false); 13 | 14 | const beforeAddValidate = text => { 15 | if (text.length < 3) { 16 | alert("too short!"); 17 | return false; 18 | } 19 | return true; 20 | }; 21 | 22 | return ( 23 |
{JSON.stringify(selected)}
26 | Disable: {JSON.stringify(disabled)}
43 | Keep Words on Backspace: {JSON.stringify(isEditOnRemove)}
52 | {JSON.stringify(selected)}
42 |