diff --git a/example/src/demos/CountrySelectorDnD.jsx b/example/src/demos/CountrySelectorDnD.jsx new file mode 100644 index 0000000..436a9f9 --- /dev/null +++ b/example/src/demos/CountrySelectorDnD.jsx @@ -0,0 +1,186 @@ +import React, { useCallback, useState } from 'react' +import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { ReactTags } from '../../../src' +import { suggestions } from '../countries' +import { closestCenter, DndContext } from '@dnd-kit/core' + +const tagToKey = (tag) => { + return `${String(tag.value)}-${tag.label}` +} + +const Handle = (props) => { + const { isDragging, ...remainingProps } = props + + return ( + + + + + + ) +} + +export function CountrySelectorDnD() { + const [selected, setSelected] = useState([suggestions[10], suggestions[121]]) + + const [options, setOptions] = useState({ + activateFirstOption: false, + allowBackspace: false, + collapseOnSelect: false, + isDisabled: false, + isInvalid: false, + }) + + const onAdd = useCallback( + (newTag) => { + setSelected([...selected, newTag]) + }, + [selected] + ) + + const onDelete = useCallback( + (index) => { + setSelected(selected.filter((_, i) => i !== index)) + }, + [selected] + ) + + const onOptionChange = useCallback( + (e) => { + setOptions({ ...options, [e.target.name]: e.target.checked }) + }, + [options] + ) + + const handleDragEnd = (event) => { + const { active, over } = event + + if (active.id !== over.id) { + setSelected((selected) => { + const oldIndex = selected.findIndex((s) => tagToKey(s) === active.id) + const newIndex = selected.findIndex((s) => tagToKey(s) === over.id) + return arrayMove(selected, oldIndex, newIndex) + }) + } + } + + const CustomTagList = ({ children, classNames, ...tagListprops }) => { + return ( + child.key)}> + + + ) + } + + const CustomTag = ({ classNames, tag, ...tagProps }) => { + const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ + id: tagToKey(tag), + }) + + const style = { + transform: CSS.Transform.toString(transform), + transition, + } + + return ( +
+ + + {tag.label} + + onDelete(selected.findIndex((s) => tagToKey(s) === tagToKey(tag)))} + > +
+ ) + } + + return ( + +

Select the countries you have visited below:

+ +
+ Options + + + + + +
+
+ View output +
+          {JSON.stringify(selected, null, 2)}
+        
+
+
+ ) +} diff --git a/example/src/index.html b/example/src/index.html index fec20d0..2a2dccb 100644 --- a/example/src/index.html +++ b/example/src/index.html @@ -156,6 +156,11 @@

Custom tag list component


+
+

Draggable tags

+
+
+

Documentation

diff --git a/example/src/main.jsx b/example/src/main.jsx index 8136d2e..339c78f 100644 --- a/example/src/main.jsx +++ b/example/src/main.jsx @@ -1,6 +1,7 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { CountrySelector } from './demos/CountrySelector' +import { CountrySelectorDnD } from './demos/CountrySelectorDnD' import { CustomTags } from './demos/CustomTags' import { CustomValidity } from './demos/CustomValidity' import { AsyncSuggestions } from './demos/AsyncSuggestions' @@ -27,4 +28,7 @@ window.onload = () => { const container6 = ReactDOM.createRoot(document.getElementById('demo-6')) container6.render() + + const container7 = ReactDOM.createRoot(document.getElementById('demo-7')) + container7.render() } diff --git a/example/src/styles.css b/example/src/styles.css index fec9c40..bdc3582 100644 --- a/example/src/styles.css +++ b/example/src/styles.css @@ -195,3 +195,63 @@ line-height: 1.5rem; color: #00000080; } + +.react-tags__dndtag { + margin: 0 0.25rem 0.25rem 0; + padding: 0.375rem 0.5rem; + border: 0; + border-radius: 3px; + background: #eaeef2; + /* match the font styles */ + font-size: inherit; + line-height: inherit; +} + +.react-tags__dndtag:hover { + color: #ffffff; + background-color: #4f46e5; +} + +.react-tags__removeTag { + cursor: pointer; +} + +.react-tags__handleTag { + margin-right: .25rem; + padding: .25rem 0.4rem; + border-radius: 5px; +} + +.react-tags__handleTag:hover { + color: #ffffff; + background-color: rgba(255, 255, 255, .1); +} + + +.react-tags__removeTag::after { + content: ''; + display: inline-block; + width: 0.65rem; + height: 0.65rem; + clip-path: polygon( + 10% 0, + 0 10%, + 40% 50%, + 0 90%, + 10% 100%, + 50% 60%, + 90% 100%, + 100% 90%, + 60% 50%, + 100% 10%, + 90% 0, + 50% 40% + ); + margin-left: 0.5rem; + font-size: 0.875rem; + background-color: #7c7d86; +} + +.react-tags__removeTag:hover::after { + background-color: #ffffff; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 50cf713..abfcc10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "7.5.0", "license": "ISC", "devDependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.0", @@ -514,6 +517,63 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -4982,6 +5042,13 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 30f32e0..8e68d4b 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,9 @@ }, "homepage": "https://github.com/i-like-robots/react-tag-autocomplete", "devDependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.0",