forked from NangoHQ/nango
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Tags input for entering scopes (NangoHQ#556)
* feat: tags input * chore: remove comment lines * fix: change tooltip text * fix: lint and type-errors
- Loading branch information
1 parent
c02b0fe
commit 7340924
Showing
3 changed files
with
113 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { forwardRef, type KeyboardEvent, useState } from 'react'; | ||
import { X } from '@geist-ui/icons'; | ||
|
||
import useSet from '../../hooks/useSet'; | ||
|
||
type TagsInputProps = Omit<JSX.IntrinsicElements['input'], 'defaultValue'> & { defaultValue: string }; | ||
|
||
const TagsInput = forwardRef<HTMLInputElement, TagsInputProps>(function TagsInput({ className, defaultValue, ...props }, ref) { | ||
const defaultScopes = !!defaultValue ? defaultValue.split(',') : []; | ||
const [enteredValue, setEnteredValue] = useState(''); | ||
const [selectedScopes, addToScopesSet, removeFromSelectedSet] = useSet<string>(defaultScopes); | ||
|
||
function handleEnter(e: KeyboardEvent<HTMLInputElement>) { | ||
//quick check for empty inputs | ||
if (e.key === 'Enter') { | ||
e.preventDefault(); | ||
if (!!e.currentTarget.value) { | ||
e.currentTarget.value = ''; | ||
handleAdd(e.currentTarget.value); | ||
} | ||
} | ||
} | ||
|
||
function handleAdd(value?: string) { | ||
if (value) { | ||
addToScopesSet(value.trim()); | ||
} else { | ||
addToScopesSet(enteredValue); | ||
setEnteredValue(''); | ||
} | ||
} | ||
|
||
function removeScope(scopeToBeRemoved: string) { | ||
removeFromSelectedSet(scopeToBeRemoved); | ||
} | ||
|
||
return ( | ||
<> | ||
<div className="flex gap-3"> | ||
<input required value={Array.from(selectedScopes.values()).join(',')} {...props} hidden /> | ||
<input | ||
ref={ref} | ||
value={enteredValue} | ||
onChange={(e) => setEnteredValue(e.currentTarget.value)} | ||
onKeyDown={handleEnter} | ||
minLength={1} | ||
className="border-border-gray bg-bg-black text-text-light-gray focus:border-white focus:ring-white block h-11 w-full appearance-none rounded-md border px-3 py-2 text-base placeholder-gray-400 shadow-sm focus:outline-none" | ||
/> | ||
<button | ||
onClick={() => handleAdd()} | ||
type="button" | ||
className="text-center px-8 text-sm font-medium bg-white text-black rounded-lg cursor-pointer" | ||
> | ||
Add | ||
</button> | ||
</div> | ||
{!!Array.from(selectedScopes.values()).length && ( | ||
<div className="px-2 pt-2 mt-3 pb-11 mb-3 flex flex-wrap rounded-lg border border-border-gray"> | ||
{Array.from(selectedScopes.values()).map((selectedScope, i) => { | ||
return ( | ||
<span | ||
key={selectedScope + i} | ||
className="flex flex-wrap gap-2 pl-4 pr-2 py-2 m-1 justify-between items-center text-sm font-medium rounded-lg cursor-pointer bg-gray-100 text-black" | ||
> | ||
{selectedScope} | ||
<X onClick={() => removeScope(selectedScope)} className="h-5 w-5" /> | ||
</span> | ||
); | ||
})} | ||
</div> | ||
)} | ||
</> | ||
); | ||
}); | ||
|
||
export default TagsInput; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { useState, useRef, useCallback } from 'react'; | ||
|
||
export default function useSet<T>(initialValue?: T[], limit?: number) { | ||
const [, setInc] = useState(false); | ||
|
||
const set = useRef(new Set<T>(initialValue)); | ||
|
||
const add = useCallback( | ||
(item: T) => { | ||
if (set.current.has(item) || (limit && Array.from(set.current.values()).length >= limit)) return; | ||
setInc((prev) => !prev); | ||
set.current.add(item); | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[setInc] | ||
); | ||
|
||
const remove = useCallback( | ||
(item: T) => { | ||
if (!set.current.has(item)) return; | ||
setInc((prev) => !prev); | ||
set.current.delete(item); | ||
}, | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
[setInc] | ||
); | ||
|
||
return [set.current, add, remove] as const; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters