Skip to content

Commit

Permalink
feat: add keyboard navigation and tag focus handling
Browse files Browse the repository at this point in the history
  • Loading branch information
JaleelB committed May 31, 2024
1 parent 78327ee commit 1d62a1c
Showing 1 changed file with 79 additions and 16 deletions.
95 changes: 79 additions & 16 deletions packages/emblor/src/tag/tag-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
const [inputValue, setInputValue] = React.useState('');
const [tagCount, setTagCount] = React.useState(Math.max(0, tags.length));
const inputRef = React.useRef<HTMLInputElement>(null);
const [activeTagIndex, setActiveTagIndex] = React.useState<number | null>(null);

if ((maxTags !== undefined && maxTags < 0) || (props.minTags !== undefined && props.minTags < 0)) {
console.warn('maxTags and minTags cannot be less than 0');
Expand All @@ -126,6 +127,15 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onInputChange?.(newValue);
};

const handleInputFocus = (event: React.FocusEvent<HTMLInputElement>) => {
setActiveTagIndex(null); // Reset active tag index when the input field gains focus
onFocus?.(event);
};

const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
onBlur?.(event);
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (delimiterList ? delimiterList.includes(e.key) : e.key === delimiter || e.key === Delimiter.Enter) {
e.preventDefault();
Expand Down Expand Up @@ -167,6 +177,53 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
}
setInputValue('');
}

switch (e.key) {
case 'Delete':
if (activeTagIndex !== null) {
e.preventDefault();
const newTags = [...tags];
newTags.splice(activeTagIndex, 1);
setTags(newTags);
setActiveTagIndex((prev) =>
newTags.length === 0 ? null : prev! >= newTags.length ? newTags.length - 1 : prev,
);
}
break;
case 'Backspace':
if (activeTagIndex !== null) {
e.preventDefault();
const newTags = [...tags];
newTags.splice(activeTagIndex, 1);
setTags(newTags);
setActiveTagIndex((prev) => (prev! === 0 ? null : prev! - 1));
}
break;
case 'ArrowRight':
e.preventDefault();
if (activeTagIndex === null) {
setActiveTagIndex(0);
} else {
setActiveTagIndex((prev) => (prev! + 1 >= tags.length ? 0 : prev! + 1));
}
break;
case 'ArrowLeft':
e.preventDefault();
if (activeTagIndex === null) {
setActiveTagIndex(tags.length - 1);
} else {
setActiveTagIndex((prev) => (prev! === 0 ? tags.length - 1 : prev! - 1));
}
break;
case 'Home':
e.preventDefault();
setActiveTagIndex(0);
break;
case 'End':
e.preventDefault();
setActiveTagIndex(tags.length - 1);
break;
}
};

const removeTag = (idToRemove: string) => {
Expand Down Expand Up @@ -227,6 +284,8 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onRemoveTag={removeTag}
direction={direction}
includeTagsInInput={includeTagsInInput}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
/>
) : (
!enableAutocomplete && (
Expand All @@ -251,6 +310,8 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onRemoveTag={removeTag}
direction={direction}
includeTagsInInput={includeTagsInInput}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
/>
<Input
ref={inputRef}
Expand All @@ -260,12 +321,12 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={onFocus}
onBlur={onBlur}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
{...inputProps}
className={cn(
className,
'border-0 h-5 bg-transparent sm:min-w-focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 w-fit',
'border-0 h-5 bg-transparent focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 w-fit',
)}
autoComplete={enableAutocomplete ? 'on' : 'off'}
list={enableAutocomplete ? 'autocomplete-options' : undefined}
Expand Down Expand Up @@ -295,9 +356,9 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
disabled={maxTags !== undefined && tags.length >= maxTags}
onChangeCapture={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={onFocus}
onBlur={onBlur}
className="w-full"
onFocus={handleInputFocus}
onBlur={handleInputBlur}
className={cn('w-full', className)}
/>
) : (
<div
Expand All @@ -320,6 +381,8 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
onRemoveTag={removeTag}
direction={direction}
includeTagsInInput={includeTagsInInput}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
/>
<CommandInput
placeholder={maxTags !== undefined && tags.length >= maxTags ? placeholderWhenFull : placeholder}
Expand All @@ -328,10 +391,10 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
disabled={maxTags !== undefined && tags.length >= maxTags}
onChangeCapture={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={onFocus}
onBlur={onBlur}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
includeTagsInInput={includeTagsInInput}
className="border-0 w-fit h-5"
className={cn('border-0 w-fit h-5', className)}
/>
</div>
)
Expand Down Expand Up @@ -360,9 +423,9 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
disabled={maxTags !== undefined && tags.length >= maxTags}
onChangeCapture={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={onFocus}
onBlur={onBlur}
className="w-full"
onFocus={handleInputFocus}
onBlur={handleInputBlur}
className={cn('w-full', className)}
/>
</TagPopover>
)}
Expand All @@ -380,8 +443,8 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={onFocus}
onBlur={onBlur}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
{...inputProps}
className={className}
autoComplete={enableAutocomplete ? 'on' : 'off'}
Expand Down Expand Up @@ -415,8 +478,8 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>((props, ref)
value={inputValue}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onFocus={onFocus}
onBlur={onBlur}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
{...inputProps}
className={className}
autoComplete={enableAutocomplete ? 'on' : 'off'}
Expand Down

0 comments on commit 1d62a1c

Please sign in to comment.